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

ParadoxGameConverters / commonItems / 12846535547

18 Jan 2025 06:41PM UTC coverage: 75.846% (-1.7%) from 77.556%
12846535547

Pull #274

github

web-flow
Merge 8b9494010 into b007cb890
Pull Request #274: Eliminate warnings

279 of 358 new or added lines in 13 files covered. (77.93%)

38 existing lines in 2 files now uncovered.

1837 of 2422 relevant lines covered (75.85%)

240.46 hits per line

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

70.9
/ModLoader/ModLoader.cpp
1
#include "ModLoader.h"
2

3
#include <external/zip/src/zip.h>
4
#include <filesystem>
5
#include <set>
6
#include <stdexcept>
7
#include <string>
8

9
#include "../CommonFunctions.h"
10
#include "../Log.h"
11
#include "../OSCompatibilityLayer.h"
12
#include "ModParser.h"
13

14

15

16
using std::filesystem::create_directories;
17
using std::filesystem::path;
18
using std::filesystem::u8path;
19

20

21

NEW
22
void commonItems::ModLoader::loadMods(const path& gameDocumentsPath, const Mods& incomingMods)
×
23
{
NEW
24
        loadMods(std::vector{gameDocumentsPath / "mod"}, incomingMods);
×
NEW
25
}
×
26

27

28
void commonItems::ModLoader::loadMods(const std::string& gameDocumentsPath, const Mods& incomingMods)
9✔
29
{
30
#pragma warning(push)
31
#pragma warning(disable : 4996)
32
        loadMods(std::vector<std::string>{gameDocumentsPath + "/mod"}, incomingMods);
18✔
33
#pragma warning(pop)
34
}
18✔
35

36

37
void commonItems::ModLoader::loadMods(const std::vector<path>& gameModPaths, const Mods& incomingMods)
1✔
38
{
39
        if (gameModPaths.empty())
1✔
40
        {
NEW
41
                Log(LogLevel::Info) << "No mod directories were provided. Skipping mod processing.";
×
NEW
42
                return;
×
43
        }
44

45

46
        if (incomingMods.empty())
1✔
47
        {
48
                // We shouldn't even be here if the save didn't have mods! Why were Mods called?
NEW
49
                Log(LogLevel::Info) << "No mods were detected in savegame. Skipping mod processing.";
×
NEW
50
                return;
×
51
        }
52

53
        // First see what we're up against. Load mod folders, and cache the mod names. We need the names as bare minimum in case
54
        // we're doing old-style name-recognition modfinding and don't have the paths in incomingMods.
55
        Log(LogLevel::Info) << "\tMods directories are:";
1✔
56
        for (path gameModPath: gameModPaths)
3✔
57
        {
58
                gameModPath.make_preferred();
2✔
59
                Log(LogLevel::Info) << "\t\t-> [" << gameModPath.string() << "]";
2✔
60
                cacheModNames(gameModPath);
2✔
61
        }
2✔
62

63
        // We enter this function with a vector of (optional) mod names and (required) mod file locations from the savegame.
64
        // We need to read all the mod files, check their paths (and potential archives for ancient mods) unpack what's
65
        // necessary, and exit with a vector of updated mod names (savegame can differ from actual mod file) and mod folder
66
        // locations.
67

68
        // The function below reads all the incoming .mod files and verifies their internal paths/archives are correct and
69
        // point to something present on disk. No unpacking yet.
70
        Log(LogLevel::Info) << "\tLoading Mod Directories.";
1✔
71
        loadModDirectories(gameModPaths, incomingMods);
1✔
72

73
        // Now we merge all detected .mod files together.
74
        Log(LogLevel::Info) << "\tDetermining Mod Usability.";
1✔
75
        auto allMods = possibleUncompressedMods;
1✔
76
        allMods.insert(allMods.end(), possibleCompressedMods.begin(), possibleCompressedMods.end());
1✔
77

78
        // With a list of all detected and matched mods, we unpack the compressed ones (if any) and store the results.
79
        for (const auto& mod: allMods)
4✔
80
        {
81
                // This invocation will unpack any compressed mods into our converter's folder, and skip already unpacked ones.
82
                const auto possibleModPath = uncompressAndReturnNewPath(mod.name);
3✔
83
                if (!possibleModPath)
3✔
84
                {
NEW
85
                        Log(LogLevel::Warning) << "\t\tFailure unpacking [" << mod.name << "], skipping this mod at your risk.";
×
NEW
86
                        continue;
×
87
                }
88

89
                // All verified mods go into usableMods
90
                Log(LogLevel::Info) << "\t\t->> Found potentially useful [" << mod.name << "]: " << possibleModPath->string();
3✔
91
                usableMods.emplace_back(Mod(mod.name, *possibleModPath, mod.dependencies, mod.replacedFolders));
3✔
92
        }
3✔
93
}
1✔
94

95

96
void commonItems::ModLoader::loadMods(const std::vector<std::string>& gameModPaths, const Mods& incomingMods)
10✔
97
{
98
#pragma warning(push)
99
#pragma warning(disable : 4996)
100
        if (gameModPaths.empty())
10✔
101
        {
102
                Log(LogLevel::Info) << "No mod directories were provided. Skipping mod processing.";
×
103
                return;
1✔
104
        }
105

106

107
        if (incomingMods.empty())
10✔
108
        {
109
                // We shouldn't even be here if the save didn't have mods! Why were Mods called?
110
                Log(LogLevel::Info) << "No mods were detected in savegame. Skipping mod processing.";
1✔
111
                return;
1✔
112
        }
113

114
        // First see what we're up against. Load mod folders, and cache the mod names. We need the names as bare minimum in case
115
        // we're doing old-style name-recognition modfinding and don't have the paths in incomingMods.
116
        Log(LogLevel::Info) << "\tMods directories are:";
9✔
117
        for (const auto& gameModPath: gameModPaths)
19✔
118
        {
119
                Log(LogLevel::Info) << "\t\t-> [" << gameModPath << "]";
10✔
120
                cacheModNames(gameModPath);
10✔
121
        }
122

123
        // We enter this function with a vector of (optional) mod names and (required) mod file locations from the savegame.
124
        // We need to read all the mod files, check their paths (and potential archives for ancient mods) unpack what's
125
        // necessary, and exit with a vector of updated mod names (savegame can differ from actual mod file) and mod folder
126
        // locations.
127

128
        // The function below reads all the incoming .mod files and verifies their internal paths/archives are correct and
129
        // point to something present on disk. No unpacking yet.
130
        Log(LogLevel::Info) << "\tLoading Mod Directories.";
9✔
131
        loadModDirectories(gameModPaths, incomingMods);
9✔
132

133
        // Now we merge all detected .mod files together.
134
        Log(LogLevel::Info) << "\tDetermining Mod Usability.";
9✔
135
        auto allMods = possibleUncompressedMods;
9✔
136
        allMods.insert(allMods.end(), possibleCompressedMods.begin(), possibleCompressedMods.end());
9✔
137

138
        // With a list of all detected and matched mods, we unpack the compressed ones (if any) and store the results.
139
        for (const auto& mod: allMods)
20✔
140
        {
141
                // This invocation will unpack any compressed mods into our converter's folder, and skip already unpacked ones.
142
                const auto possibleModPath = uncompressAndReturnNewPath(mod.name);
11✔
143
                if (!possibleModPath)
11✔
144
                {
145
                        Log(LogLevel::Warning) << "\t\tFailure unpacking [" << mod.name << "], skipping this mod at your risk.";
1✔
146
                        continue;
1✔
147
                }
148

149
                // All verified mods go into usableMods
150
                Log(LogLevel::Info) << "\t\t->> Found potentially useful [" << mod.name << "]: " << possibleModPath->string() + "/";
10✔
151
                usableMods.emplace_back(Mod(mod.name, possibleModPath->string() + "/", mod.dependencies, mod.replacedFolders));
10✔
152
        }
11✔
153
#pragma warning(pop)
154
}
9✔
155

156

157
void commonItems::ModLoader::loadModDirectories(const std::vector<path>& gameModPaths, const Mods& incomingMods)
1✔
158
{
159
        std::set<path> diskModNames;
1✔
160
        for (path modPath: gameModPaths)
3✔
161
        {
162
                modPath.make_preferred();
2✔
163
                for (const auto& diskModName: GetAllFilesInFolder(modPath))
11✔
164
                {
165
                        diskModNames.insert(diskModName);
9✔
166
                }
2✔
167
        }
2✔
168

169
        for (auto mod: incomingMods)
4✔
170
        {
171
                // If we don't have a loaded mod path but have it in our cache, might as well fix it.
172
                if (mod.path.empty() && modCache.contains(mod.name))
3✔
173
                {
174
                        mod.path = modCache.at(mod.name);
3✔
175
                }
176

177
                const auto trimmedModFileName = mod.path.filename();
3✔
178

179
                // We either have the path as the reference point (in which case name we'll read ourselves), or the name, in which case we looked up the
180
                // cached map for the path.
181
                // If we have neither, that's unusable.
182

183
                if (!diskModNames.contains(trimmedModFileName) && !modCache.contains(mod.name))
3✔
184
                {
NEW
185
                        if (mod.name.empty())
×
NEW
186
                                Log(LogLevel::Warning) << "\t\tSavegame uses mod at [" << mod.path
×
NEW
187
                                                                                          << "] which is not present on disk. Skipping at your risk, but this can greatly affect conversion.";
×
NEW
188
                        else if (mod.path.empty())
×
NEW
189
                                Log(LogLevel::Warning) << "\t\tSavegame uses [" << mod.name
×
NEW
190
                                                                                          << "] which is not present on disk. Skipping at your risk, but this can greatly affect conversion.";
×
191
                        else
NEW
192
                                Log(LogLevel::Warning) << "\t\tSavegame uses [" << mod.name << "] at [" << mod.path
×
NEW
193
                                                                                          << "] which is not present on disk. Skipping at your risk, but this can greatly affect conversion.";
×
NEW
194
                        continue;
×
195
                }
196

197
                // if we do have a path incoming from the save, just make sure it's not some abnormality.
198
                if (!trimmedModFileName.empty() && trimmedModFileName.extension() != ".mod")
3✔
199
                {
200
                        // Vic3 mods won't have .mod, but they will be in the cache and lack .mod there
201
                        if (!modCache.contains(mod.name) || modCache.at(mod.name).extension() == ".mod")
2✔
NEW
202
                                continue; // shouldn't be necessary but just in case.
×
203
                }
204

205
                // Attempt parsing .mod file
206
                for (path gameModPath: gameModPaths)
4✔
207
                {
208
                        gameModPath.make_preferred();
4✔
209
                        if (!trimmedModFileName.empty() && trimmedModFileName.extension() == ".mod")
4✔
210
                        {
211
                                const path mod_file_location = gameModPath / trimmedModFileName;
1✔
212

213
                                if (!DoesFileExist(mod_file_location))
1✔
214
                                {
NEW
215
                                        continue;
×
216
                                }
217

218
                                ModParser theMod;
1✔
219
                                try
220
                                {
221
                                        theMod.parseMod(mod_file_location);
1✔
222
                                }
NEW
223
                                catch (std::exception&)
×
224
                                {
NEW
225
                                        Log(LogLevel::Warning) << "\t\tError while reading [" << mod_file_location << "]! Mod will not be useable for conversions.";
×
NEW
226
                                        continue;
×
NEW
227
                                }
×
228
                                processLoadedMod(theMod, mod.name, trimmedModFileName, mod.path, gameModPath);
1✔
229
                                break;
1✔
230
                        }
2✔
231
                        else
232
                        {
233
                                // Vic3 mods
234

235
                                path mod_folder;
3✔
236
                                for (auto itr = mod.path.begin(); itr != mod.path.end(); ++itr)
9✔
237
                                {
238
                                        mod_folder = *itr;
6✔
239
                                }
240
                                const path metadata_location = gameModPath / mod_folder / ".metadata/metadata.json";
3✔
241
                                if (!DoesFileExist(metadata_location))
3✔
242
                                {
243
                                        continue;
1✔
244
                                }
245

246
                                ModParser theMod;
2✔
247
                                try
248
                                {
249
                                        theMod.parseMetadata(metadata_location);
2✔
250
                                }
NEW
251
                                catch (std::exception&)
×
252
                                {
NEW
253
                                        Log(LogLevel::Warning) << "\t\tError while reading [" << metadata_location << "]! Mod will not be useable for conversions.";
×
NEW
254
                                        continue;
×
NEW
255
                                }
×
256
                                processLoadedMod(theMod, mod.name, mod_folder, mod.path, gameModPath, true);
2✔
257
                                break;
2✔
258
                        }
8✔
259
                }
4✔
260
        }
3✔
261
}
1✔
262

263

264
void commonItems::ModLoader::loadModDirectories(const std::vector<std::string>& gameModPaths, const Mods& incomingMods)
9✔
265
{
266
#pragma warning(push)
267
#pragma warning(disable : 4996)
268
        std::set<std::string> diskModNames;
9✔
269
        for (const auto& modPath: gameModPaths)
19✔
270
        {
271
                for (const auto& diskModName: GetAllFilesInFolder(modPath))
91✔
272
                {
273
                        diskModNames.insert(diskModName);
81✔
274
                }
10✔
275
        }
276

277
        for (auto mod: incomingMods)
23✔
278
        {
279
                // If we don't have a loaded mod path but have it in our cache, might as well fix it.
280
                if (mod.path.empty() && modCache.contains(mod.name))
14✔
281
                {
282
                        mod.path = modCache.at(mod.name);
6✔
283
                }
284

285
                const auto trimmedModFileName = trimPath(mod.path.string());
14✔
286

287
                // We either have the path as the reference point (in which case name we'll read ourselves), or the name, in which case we looked up the
288
                // cached map for the path.
289
                // If we have neither, that's unusable.
290

291
                if (!diskModNames.contains(trimmedModFileName) && !modCache.contains(mod.name))
14✔
292
                {
293
                        if (mod.name.empty())
1✔
294
                                Log(LogLevel::Warning) << "\t\tSavegame uses mod at [" << mod.path
×
295
                                                                                          << "] which is not present on disk. Skipping at your risk, but this can greatly affect conversion.";
×
296
                        else if (mod.path.empty())
1✔
297
                                Log(LogLevel::Warning) << "\t\tSavegame uses [" << mod.name
×
298
                                                                                          << "] which is not present on disk. Skipping at your risk, but this can greatly affect conversion.";
×
299
                        else
300
                                Log(LogLevel::Warning) << "\t\tSavegame uses [" << mod.name << "] at [" << mod.path
2✔
301
                                                                                          << "] which is not present on disk. Skipping at your risk, but this can greatly affect conversion.";
1✔
302
                        continue;
1✔
303
                }
304

305
                // if we do have a path incoming from the save, just make sure it's not some abnormality.
306
                if (!trimmedModFileName.empty() && getExtension(trimmedModFileName) != "mod")
13✔
307
                {
308
                        // Vic3 mods won't have .mod, but they will be in the cache and lack .mod there
309
                        if (!modCache.contains(mod.name) || modCache.at(mod.name).string().ends_with(".mod"))
4✔
310
                                continue; // shouldn't be necessary but just in case.
×
311
                }
312

313
                // Attempt parsing .mod file
314
                for (const auto& gameModPath: gameModPaths)
14✔
315
                {
316
                        if (!trimmedModFileName.empty() && trimmedModFileName.ends_with(".mod"))
14✔
317
                        {
318
                                const std::string mod_file_location = gameModPath + "/" + trimmedModFileName;
9✔
319

320
                                if (!DoesFileExist(mod_file_location))
9✔
321
                                {
322
                                        continue;
×
323
                                }
324

325
                                ModParser theMod;
9✔
326
                                try
327
                                {
328
                                        theMod.parseMod(mod_file_location);
9✔
329
                                }
330
                                catch (std::exception&)
×
331
                                {
332
                                        Log(LogLevel::Warning) << "\t\tError while reading [" << mod_file_location << "]! Mod will not be useable for conversions.";
×
333
                                        continue;
×
334
                                }
×
335
                                processLoadedMod(theMod, mod.name, trimmedModFileName, mod.path, gameModPath);
9✔
336
                                break;
9✔
337
                        }
18✔
338
                        else
339
                        {
340
                                // Vic3 mods
341
                                std::string mod_folder;
5✔
342
                                for (auto itr = mod.path.begin(); itr != mod.path.end(); ++itr)
15✔
343
                                {
344
                                        mod_folder = itr->string();
10✔
345
                                }
346
                                const std::string metadata_location = gameModPath + "/" + mod_folder + "/.metadata/metadata.json";
5✔
347
                                if (!DoesFileExist(metadata_location))
5✔
348
                                {
349
                                        continue;
1✔
350
                                }
351

352
                                ModParser theMod;
4✔
353
                                try
354
                                {
355
                                        theMod.parseMetadata(metadata_location);
4✔
356
                                }
357
                                catch (std::exception&)
×
358
                                {
359
                                        Log(LogLevel::Warning) << "\t\tError while reading [" << metadata_location << "]! Mod will not be useable for conversions.";
×
360
                                        continue;
×
361
                                }
×
362
                                processLoadedMod(theMod, mod.name, mod_folder, mod.path, gameModPath, true);
4✔
363
                                break;
4✔
364
                        }
14✔
365
                }
366
        }
15✔
367
#pragma warning(pop)
368
}
9✔
369

370

371
void commonItems::ModLoader::cacheModNames(const path& gameDocumentsPath)
12✔
372
{
373
        if (!DoesFolderExist(gameDocumentsPath))
12✔
NEW
374
                throw std::invalid_argument("Mods directory path is invalid! Is it at: " + gameDocumentsPath.string() + " ?");
×
375

376
        for (const auto& diskModFile: GetAllFilesInFolder(gameDocumentsPath))
102✔
377
        {
378
                if (diskModFile.extension() != ".mod")
90✔
379
                        continue;
20✔
380
                ModParser theMod;
70✔
381
                const auto trimmedModFileName = diskModFile.filename();
70✔
382
                try
383
                {
384
                        theMod.parseMod(gameDocumentsPath / trimmedModFileName);
70✔
385
                }
386
                catch (std::exception&)
×
387
                {
388
                        Log(LogLevel::Warning) << "\t\t\t! Error while caching [" << gameDocumentsPath << "/" << trimmedModFileName
×
389
                                                                                  << "]! Mod will not be useable for conversions.";
×
390
                        continue;
×
391
                }
×
392
                if (theMod.isValid())
70✔
393
                        modCache.emplace(theMod.getName(), diskModFile);
50✔
394
                else
395
                        Log(LogLevel::Warning) << "\t\t\t! Mod at [" << diskModFile << "] is invalid.";
20✔
396
        }
82✔
397

398
        for (const auto& possible_mod_folder: GetAllSubfolders(gameDocumentsPath))
54✔
399
        {
400
                const path metadata_location = gameDocumentsPath / possible_mod_folder / ".metadata/metadata.json";
42✔
401
                try
402
                {
403
                        if (!DoesFileExist(metadata_location))
42✔
404
                        {
405
                                continue;
10✔
406
                        }
407
                }
408
                catch (std::exception&)
×
409
                {
NEW
410
                        Log(LogLevel::Warning) << "\t\t\t! Invalid mod name [" << possible_mod_folder.string() << "] caused exception";
×
411
                        continue;
×
412
                }
×
413

414
                ModParser theMod;
32✔
415
                try
416
                {
417
                        theMod.parseMetadata(metadata_location);
32✔
418
                }
419
                catch (std::exception&)
×
420
                {
NEW
421
                        Log(LogLevel::Warning) << "\t\t\t! Error while caching [" << possible_mod_folder.string() << "]! Mod will not be useable for conversions.";
×
422
                        continue;
×
423
                }
×
424
                if (theMod.isValid())
32✔
425
                {
426
                        modCache.emplace(theMod.getName(), theMod.getFilesystemPath());
22✔
427
                }
428
                else
429
                        Log(LogLevel::Warning) << "\t\t\t! Mod at [" << (gameDocumentsPath / possible_mod_folder).string() << "] has invalid metadata.";
10✔
430
        }
54✔
431
}
12✔
432

433

434
void commonItems::ModLoader::processLoadedMod(ModParser& theMod,
16✔
435
         const std::string& modName,
436
         const path& modFileName,
437
         const path& modPath,
438
         const path& gameModPath,
439
         const bool metaMod)
440
{
441
        if (!theMod.isValid())
16✔
442
        {
443
                Log(LogLevel::Warning) << "\t\t\t! Mod at [" << (gameModPath / modFileName).make_preferred().string() << "] does not look valid.";
1✔
444
                return;
1✔
445
        }
446

447
        // Expand relative into absolute paths (or at least relative to the converter). THIS IS IMPORTANT for any mod that still has "mod/something" as path!
448
        if (!theMod.isCompressed() && !DoesFolderExist(theMod.getFilesystemPath()))
15✔
449
        {
450
                // Maybe we have a relative path
451
                const auto trimmedPath = theMod.getFilesystemPath().filename();
13✔
452
                if (DoesFolderExist(gameModPath / trimmedPath))
13✔
453
                {
454
                        // fix this.
455
                        theMod.setPath(gameModPath / trimmedPath);
12✔
456
                }
457
                else
458
                {
459
                        warnForInvalidPath(theMod, modName, modPath);
1✔
460
                        return;
1✔
461
                }
462
        }
13✔
463
        else if (theMod.isCompressed() && !DoesFileExist(theMod.getFilesystemPath()))
2✔
464
        {
465
                // Maybe we have a relative path
466
                const auto trimmedPath = theMod.getFilesystemPath().filename();
2✔
467
                if (DoesFileExist(gameModPath / trimmedPath))
2✔
468
                {
469
                        // fix this.
470
                        theMod.setPath(gameModPath / trimmedPath);
2✔
471
                }
472
                else
473
                {
474
                        warnForInvalidPath(theMod, modName, modPath);
×
475
                        return;
×
476
                }
477
        }
2✔
478

479
        // file under category.
480
        fileUnderCategory(theMod, (gameModPath / modFileName).make_preferred(), metaMod);
14✔
481
}
482

483
void commonItems::ModLoader::warnForInvalidPath(const ModParser& theMod, const std::string& name, const path& path)
1✔
484
{
485
        if (name.empty())
1✔
NEW
486
                Log(LogLevel::Warning) << "\t\tMod at [" + path.string() + "] points to [" + theMod.getFilesystemPath().string() +
×
487
                                                                                                "] which does not exist! Skipping at your risk, but this can greatly affect conversion.";
×
488
        else
489
                Log(LogLevel::Warning) << "\t\tMod [" << name
2✔
490
                                                                          << "] at [" + path.string() + "] points to [" + theMod.getFilesystemPath().string() +
2✔
491
                                                                                                "] which does not exist! Skipping at your risk, but this can greatly affect conversion.";
1✔
492
}
1✔
493

494
void commonItems::ModLoader::fileUnderCategory(const ModParser& theMod, const path& path, const bool metaMod)
14✔
495
{
496
        if (!metaMod)
14✔
497
        {
498
                if (!theMod.isCompressed())
8✔
499
                {
500
                        possibleUncompressedMods.emplace_back(
6✔
501
                                 Mod(theMod.getName(), theMod.getFilesystemPath(), theMod.getDependencies(), theMod.getFilesystemReplacedPaths()));
12✔
502
                        Log(LogLevel::Info) << "\t\tFound a potential mod [" << theMod.getName() << "] with a mod file at [" << path << "] and itself at ["
12✔
503
                                                                          << theMod.getFilesystemPath() << "].";
6✔
504
                }
505
                else
506
                {
507
                        possibleCompressedMods.emplace_back(Mod(theMod.getName(), theMod.getFilesystemPath(), theMod.getDependencies(), theMod.getFilesystemReplacedPaths()));
2✔
508
                        Log(LogLevel::Info) << "\t\tFound a compressed mod [" << theMod.getName() << "] with a mod file at [" << path << "] and itself at ["
4✔
509
                                                                          << theMod.getFilesystemPath() << "].";
2✔
510
                }
511
        }
512
        else
513
        {
514
                possibleUncompressedMods.emplace_back(Mod(theMod.getName(), theMod.getFilesystemPath(), theMod.getDependencies(), theMod.getFilesystemReplacedPaths()));
6✔
515
                Log(LogLevel::Info) << "\t\tFound a potential meta-mod [" << theMod.getName() << "] at [" << theMod.getFilesystemPath() << "].";
6✔
516
        }
517
}
14✔
518

519
std::optional<path> commonItems::ModLoader::uncompressAndReturnNewPath(const std::string& modName) const
14✔
520
{
521
        for (const auto& mod: possibleUncompressedMods)
20✔
522
        {
523
                if (mod.name == modName)
18✔
524
                        return mod.path;
12✔
525
        }
526

527
        for (const auto& compressedMod: possibleCompressedMods)
2✔
528
        {
529
                if (compressedMod.name != modName)
2✔
530
                        continue;
×
531

532
                const auto uncompressedName = compressedMod.path.stem();
2✔
533

534
                create_directories("mods/");
2✔
535

536
                if (!DoesFolderExist("mods" / uncompressedName))
2✔
537
                {
538
                        Log(LogLevel::Info) << "\t\tUncompressing: " << compressedMod.path;
2✔
539
                        if (!extractZip(compressedMod.path, "mods" / uncompressedName))
2✔
540
                        {
541
                                Log(LogLevel::Warning) << "We're having trouble automatically uncompressing your mod.";
1✔
542
                                Log(LogLevel::Warning) << "Please, manually uncompress: " << compressedMod.path;
1✔
543
                                Log(LogLevel::Warning) << "Into converter's folder, mods/" << uncompressedName << " subfolder.";
1✔
544
                                Log(LogLevel::Warning) << "Then run the converter again. Thank you and good luck.";
1✔
545
                                return std::nullopt;
1✔
546
                        }
547
                }
548

549
                if (DoesFolderExist("mods" / uncompressedName))
1✔
550
                {
551
                        return "mods" / uncompressedName;
1✔
552
                }
553
                return std::nullopt;
×
554
        }
2✔
555

556
        return std::nullopt;
×
557
}
558

559
bool commonItems::ModLoader::extractZip(const path& archive, const path& path) const
2✔
560
{
561
        create_directories(path);
2✔
562

563
        const int result = zip_extract(archive.string().c_str(), path.string().c_str(), NULL, NULL);
2✔
564

565
        if (result != 0)
2✔
566
        {
567
                remove_all(path);
1✔
568
                return false;
1✔
569
        }
570

571
        return true;
1✔
572
}
573

574

575
void commonItems::ModLoader::sortMods()
×
576
{
577
        // using Kahn's algorithm - https://en.wikipedia.org/wiki/Topological_sorting#Kahn's_algorithm
578
        auto unsortedMods = usableMods;
×
579

580
        // track incoming edges
581
        std::map<std::string, std::set<std::string>> incomingDependencies;
×
582
        for (const auto& mod: unsortedMods)
×
583
        {
584
                for (const auto& dependency: mod.dependencies)
×
585
                {
586
                        if (auto [itr, inserted] = incomingDependencies.emplace(dependency, std::set{mod.name}); !inserted)
×
587
                        {
588
                                itr->second.insert(mod.name);
×
589
                        }
590
                }
591
        }
592

593
        // add mods with no incoming edges to the sorted mods
594
        Mods sortedMods;
×
595
        while (!unsortedMods.empty())
×
596
        {
597
                auto itr = unsortedMods.begin();
×
598
                while (incomingDependencies.contains(itr->name))
×
599
                {
600
                        ++itr;
×
601
                        if (itr == unsortedMods.end())
×
602
                        {
603
                                throw std::invalid_argument("A mod dependency was missing.");
×
604
                        }
605
                }
606

607
                sortedMods.push_back(*itr);
×
608

609
                for (const auto& dependencyName: itr->dependencies)
×
610
                {
611
                        auto dependency = incomingDependencies.find(dependencyName);
×
612
                        dependency->second.erase(itr->name);
×
613
                        if (dependency->second.empty())
×
614
                        {
615
                                incomingDependencies.erase(dependencyName);
×
616
                        }
617
                }
618

619
                unsortedMods.erase(itr);
×
620
        }
621

622
        usableMods = sortedMods;
×
623
}
×
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

© 2025 Coveralls, Inc