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

PredatorCZ / PreCore / 561

31 Aug 2025 01:43PM UTC coverage: 52.372% (-0.8%) from 53.212%
561

push

github

PredatorCZ
update includes and types

1 of 1 new or added line in 1 file covered. (100.0%)

344 existing lines in 10 files now uncovered.

4128 of 7882 relevant lines covered (52.37%)

10524.13 hits per line

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

1.86
/src/app/context.cpp
1
/*  Spike is universal dedicated module handler
2

3
    Copyright 2021-2024 Lukas Cone
4

5
    Licensed under the Apache License, Version 2.0 (the "License");
6
    you may not use this file except in compliance with the License.
7
    You may obtain a copy of the License at
8

9
        http://www.apache.org/licenses/LICENSE-2.0
10

11
    Unless required by applicable law or agreed to in writing, software
12
    distributed under the License is distributed on an "AS IS" BASIS,
13
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
    See the License for the specific language governing permissions and
15
    limitations under the License.
16
*/
17

18
#include "spike/app/context.hpp"
19
#include "spike/io/binwritter.hpp"
20
#include "spike/io/directory_scanner.hpp"
21
#include "spike/master_printer.hpp"
22
#include "spike/reflect/reflector_xml.hpp"
23
#include "spike/type/tchar.hpp"
24
#include "spike/util/pugiex.hpp"
25
#include <algorithm>
26
#include <chrono>
27
#include <sstream>
28
#include <thread>
29

30
#if defined(_MSC_VER) || defined(__MINGW64__)
31
#include "spike/type/tchar.hpp"
32
#include <windows.h>
33
#undef GetClassName
34
auto dlsym(void *handle, const char *name) {
35
  return GetProcAddress((HMODULE)handle, name);
36
}
37

38
void dlclose(void *handle) { FreeLibrary((HMODULE)handle); }
39

40
auto dlerror() {
41
  LPVOID lpMsgBuf;
42
  DWORD dw = GetLastError();
43

44
  FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
45
                    FORMAT_MESSAGE_IGNORE_INSERTS,
46
                NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
47
                (LPTSTR)&lpMsgBuf, 0, NULL);
48

49
  std::string retVal = std::to_string((LPTSTR)lpMsgBuf);
50
  LocalFree(lpMsgBuf);
51
  return retVal;
52
}
53
#else
54
#include <dlfcn.h>
55
#endif
56

57
MainAppConfFriend mainSettings{};
58
CLISettings cliSettings{};
59

60
// NOTE: ReflDesc flags, used for guis and logs
61
// MAX:n - Maximum number limit (numbers only)
62
// MIN:n - Minimum number limit (numbers only)
63
// HIDDEN - Hide text value (strings only)
64
// FILEPATH - Mark string as filepath for all files
65
// FILEPATH:<ext0>;<ext1>;<extn> - Mark string as filepath only for specified
66
//                                 extensions/patterns
67
// FOLDER - Mark string as folder
68

69
REFLECT(CLASS(MainAppConfFriend),
4✔
70
        MEMBERNAME(generateLog, "generate-log", "L",
71
                   ReflDesc{"Will generate text log of console output inside "
72
                            "application location."}),
73
        MEMBER(verbosity, "v",
74
               ReflDesc{"Prints more information per level.", "MAX:3"}),
75
        MEMBERNAME(extractSettings, "extract-settings"),
76
        MEMBERNAME(compressSettings, "compress-settings"),
77
        MEMBERNAME(texelSettings, "texel-settings"))
78

79
REFLECT(CLASS(CLISettings),
4✔
80
        MEMBER(out, ReflDesc{"Output folder for processed files", "FOLDER"}))
81

82
REFLECT(
4✔
83
    CLASS(ExtractConf),
84
    MEMBERNAME(
85
        folderPerArc, "folder-per-archive", "F",
86
        ReflDesc{
87
            "When extracting, create folder that uses input archive's name as "
88
            "output dir."}),
89
    MEMBERNAME(makeZIP, "create-zip", "Z",
90
               ReflDesc{"Pack extracted files inside ZIP file named after "
91
                        "input archive. Your HDD will thank you."}), )
92

93
REFLECT(
4✔
94
    CLASS(CompressConf),
95
    MEMBERNAME(ratioThreshold, "ratio-threshold", "c",
96
               ReflDesc{
97
                   "Writes compressed data only when compression ratio is less "
98
                   "than specified threshold [0 - 100]%",
99
                   "MAX:100"}),
100
    MEMBERNAME(minFileSize, "min-file-size", "m",
101
               ReflDesc{"Files that are smaller than specified size won't be "
102
                        "compressed."}), );
103

104
REFLECT(ENUMERATION(CubemapFace), ENUM_MEMBER(NONE), ENUM_MEMBER(Right),
3✔
105
        ENUM_MEMBER(Left), ENUM_MEMBER(Up), ENUM_MEMBER(Down),
106
        ENUM_MEMBER(Front), ENUM_MEMBER(Back));
107

108
REFLECT(
2✔
109
    ENUMERATION(TexelContextFormat),
110
    ENUM_MEMBERDESC(
111
        DDS_Legacy,
112
        R"(Use legacy dds container but decode to uncompressed formats if necessary
113
Supported legacy formats:
114
  BC1, BC2, BC3, Grayscale, RGB8, RGBA8, RGB565, RGBA4
115
Other formats will be converted to 8bit channeled format like:
116
  BC4 -> Grayscale
117
  BC5 -> RGX8
118
  BC7 -> RGBA8 or RGB8
119
Support for mipmaps, cubemaps, volumetrics
120
snorm will be converted to unorm)"),
121
    ENUM_MEMBERDESC(DDS,
122
                    R"(dds, data format is unchanged
123
PVRTC and ETC will be converted to RGBA8
124
Support for mipmaps, cubemaps, arrays, volumetrics)"),
125
    ENUM_MEMBERDESC(QOI_BMP,
126
                    R"(Decode to Quite OK Image format.
127
Only for RGX, RGB and RGBA
128
2 channels will be converted to RGX8
129
Decode to BMP for P8, P4, Grayscale,
130
No mipmap support)"),
131
    ENUM_MEMBERDESC(QOI,
132
                    R"(Decode to Quite OK Image format.
133
Normal formats RGB and RGBA are kept instact
134
2 channels will be converted to RGX8
135
8bit (P8) and 4bit (P4) palletes will be expanded to RGB or RGBA
136
Grayscale will be expanded to RGB
137
No mipmap support)"));
138

139
REFLECT(CLASS(TexelConf),
4✔
140
        MEMBERNAME(outputFormat, "output-format",
141
                   ReflDesc{"Specify output format for images"}),
142
        MEMBERNAME(cubemapToEquirectangular, "single-cube",
143
                   ReflDesc{"Convert cubemaps into equirectangular layout"}),
144
        MEMBERNAME(processMipMaps, "process-mipmaps",
145
                   ReflDesc{"Save only largest mipmap for each mipmap chain"}))
146

147
struct ReflectedInstanceFriend : ReflectedInstance {
148
  const reflectorStatic *Refl() const { return rfStatic; }
×
149
};
150

151
struct VersionHandler {
×
152
  uint32 versions[4]{};
153
  const std::string *path;
154

155
  bool operator<(const VersionHandler &other) const {
156
    if (versions[0] == other.versions[0]) {
×
157
      if (versions[1] == other.versions[1]) {
×
158
        if (versions[2] == other.versions[2]) {
×
159
          return versions[3] < other.versions[3];
×
160
        } else {
161
          return versions[2] < other.versions[2];
×
162
        }
163
      } else {
164
        return versions[1] < other.versions[1];
×
165
      }
166
    } else {
167
      return versions[0] < other.versions[0];
×
168
    }
169
  }
170
};
171

172
APPContext::APPContext(const char *moduleName_, const std::string &appFolder_,
×
173
                       const std::string &appName_)
×
174
    : appFolder(appFolder_), appName(appName_) {
×
175
  moduleName = moduleName_;
×
176

177
  auto modulePath = [&] {
×
178
    DirectoryScanner esmScan;
×
179
    esmScan.AddFilter((std::string(1, '^') + moduleName) + "*.spk$");
×
180
    esmScan.Scan(appFolder);
×
181
    std::vector<VersionHandler> versionedFiles;
×
182

183
    for (auto &f : esmScan) {
×
184
      const size_t lastDotPos = f.find_last_of('.');
185
      const size_t slashPos = f.find_last_of('/');
186
      std::string_view extension(f.data() + lastDotPos);
187
      std::string_view fileName(f.data() + slashPos, lastDotPos - slashPos);
×
188
      char *nextDot = nullptr;
×
189
      const size_t versionDotPos = fileName.find_first_of('.');
190

191
      if (versionDotPos == fileName.npos) {
×
192
        continue;
×
193
      }
194

195
      const char *versionBegin = fileName.data() + versionDotPos;
×
196
      size_t curIndex = 0;
×
197
      VersionHandler currentHandler;
198
      currentHandler.path = &f;
×
199
      auto &versions = currentHandler.versions;
200

201
      auto ChooseOne = [&] {
×
202
        if (*versionBegin != '.') {
×
203
          return false;
204
        }
205

206
        versionBegin++;
×
207
        const uint32 newVersion = std::strtoul(versionBegin, &nextDot, 10);
×
208

209
        if (versionBegin == nextDot) {
×
210
          return false;
211
        }
212

213
        versionBegin = nextDot;
×
214
        versions[curIndex++] = newVersion;
×
215

216
        return true;
×
217
      };
218

219
      if (ChooseOne()) {
×
220
        if (ChooseOne()) {
×
221
          if (ChooseOne()) {
×
222
            ChooseOne();
×
223
          }
224
        }
225
      }
226

227
      versionedFiles.push_back(currentHandler);
×
228
    }
229

230
    std::sort(versionedFiles.begin(), versionedFiles.end());
×
231

232
    if (versionedFiles.empty()) {
×
233
      throw std::runtime_error(std::string("Couldn't find module: ") +
×
234
                               moduleName);
×
235
    }
236

237
    return *versionedFiles.back().path;
×
238
  }();
×
239

240
  auto postError = [] {
×
241
    throw std::runtime_error(std::string("APPContext Error: ") + dlerror());
×
242
  };
243

244
  auto assign = [&](auto &value, auto name) {
×
245
    using type_ = std::decay_t<decltype(value)>;
246
    value = reinterpret_cast<type_>(dlsym(dlHandle, name));
×
247

248
    if (!value) {
×
249
      postError();
×
250
    }
251
  };
252

253
  auto tryAssign = [&](auto &value, auto name) {
254
    using type_ = typename std::decay_t<decltype(value)>::value_type;
255
    return value = reinterpret_cast<type_>(dlsym(dlHandle, name));
×
256
  };
257

258
  if (mainSettings.verbosity > 1) {
×
259
    printinfo("Module open: " << modulePath);
×
260
  }
261

262
#if defined(_MSC_VER) || defined(__MINGW64__)
263
  auto modPath = ToTSTRING(modulePath);
264
  dlHandle = LoadLibrary(modPath.data());
265
#else
266
  dlHandle = dlopen(modulePath.data(), RTLD_NOW);
×
267
#endif
268
  if (!dlHandle) {
×
269
    postError();
×
270
  }
271

272
  func<decltype(AppInitModule)> InitModule;
273
  assign(InitModule, "AppInitModule");
×
274
  AppInfo_s *info_ = InitModule();
×
275
  info = info_;
×
276

277
  if (info->contextVersion != AppInfo_s::CONTEXT_VERSION) {
×
278
    throw std::runtime_error("Module context version mismatch!");
×
279
  }
280

281
  info_->internalSettings = &mainSettings;
×
282

283
  tryAssign(AdditionalHelp, "AppAdditionalHelp");
284
  tryAssign(InitContext, "AppInitContext");
285
  tryAssign(FinishContext, "AppFinishContext");
286
  tryAssign(NewArchive, "AppNewArchive");
287
  tryAssign(ProcessFile, "AppProcessFile");
288
  tryAssign(ExtractStat, "AppExtractStat");
289

290
  if (NewArchive && ProcessFile) {
×
291
    throw std::logic_error("Module uses 2 or more contexts!");
×
292
  }
293
}
294

295
APPContext::APPContext(APPContext &&other)
×
296
    : APPContextCopyData(other), appFolder(std::move(other.appFolder)),
×
297
      appName(std::move(other.appName)) {
×
298
  if (dlHandle && dlHandle != other.dlHandle) {
×
299
    dlclose(dlHandle);
×
300
  }
301
  dlHandle = other.dlHandle;
×
302
  std::construct_at(&other);
303
}
304

305
APPContext &APPContext::operator=(APPContext &&other) {
×
306
  static_cast<APPContextCopyData &>(*this) = other;
307
  appFolder = std::move(other.appFolder);
×
308
  appName = std::move(other.appName);
×
309
  if (dlHandle && dlHandle != other.dlHandle) {
×
310
    dlclose(dlHandle);
×
311
  }
312
  dlHandle = other.dlHandle;
×
313
  std::construct_at(&other);
314
  return *this;
×
315
}
316

317
APPContext::~APPContext() {
×
318
  if (dlHandle) {
×
319
    dlclose(dlHandle);
×
320
  }
321
}
322

323
class ReflectorFriend : public Reflector {
324
public:
325
  using Reflector::GetReflectedInstance;
326
};
327

328
class ReflectorMemberFriend : public ReflectorMember {
329
public:
330
  using ReflectorMember::operator=;
UNCOV
331
  ReflectedInstanceFriend Ref() const { return ReflectedInstanceFriend{data}; }
×
332
  operator const ReflType &() const { return Ref().Refl()->types[id]; }
×
333
};
334

335
static auto &MainSettings() {
336
  static ReflectorWrap<MainAppConfFriend> wrap(mainSettings);
337
  return reinterpret_cast<ReflectorFriend &>(wrap);
×
338
}
339

340
static auto &ExtractSettings() {
341
  static ReflectorWrap<ExtractConf> wrap(mainSettings.extractSettings);
342
  return reinterpret_cast<ReflectorFriend &>(wrap);
×
343
}
344

345
static auto &CompressSettings() {
346
  static ReflectorWrap<CompressConf> wrap(mainSettings.compressSettings);
347
  return reinterpret_cast<ReflectorFriend &>(wrap);
×
348
}
349

350
static auto &TexelSettings() {
351
  static ReflectorWrap<TexelConf> wrap(mainSettings.texelSettings);
352
  return reinterpret_cast<ReflectorFriend &>(wrap);
×
353
}
354

355
static auto &CliSettings() {
356
  static ReflectorWrap<CLISettings> wrap(cliSettings);
UNCOV
357
  return reinterpret_cast<ReflectorFriend &>(wrap);
×
358
}
359

360
static const reflectorStatic *RTTI(const ReflectorFriend &ref) {
361
  auto rawRTTI = ref.GetReflectedInstance();
×
362
  return static_cast<const ReflectedInstanceFriend &>(rawRTTI).Refl();
363
}
364

UNCOV
365
void APPContext::ResetSwitchSettings() {
×
366
  if (info->settings) {
×
367
    const size_t numValues = Settings().NumReflectedValues();
×
UNCOV
368
    for (size_t i = 0; i < numValues; i++) {
×
UNCOV
369
      ReflType rType = ReflectorMemberFriend{Settings()[i]};
×
370

UNCOV
371
      if (rType.type == REFType::Bool) {
×
372
        Settings()[i] = "false";
×
373
      }
374
    }
375
  }
376

UNCOV
377
  mainSettings.generateLog = mainSettings.extractSettings.folderPerArc =
×
UNCOV
378
      mainSettings.extractSettings.makeZIP = false;
×
379
}
380

UNCOV
381
int APPContext::ApplySetting(std::string_view key, std::string_view value) {
×
382
  JenHash keyHash(key);
383
  ReflectorFriend *refl = nullptr;
384
  static ReflectorFriend *settings[]{
385
      /**/ //
386
      &Settings(),
387
      &MainSettings(),
×
UNCOV
388
      &CliSettings(),
×
UNCOV
389
      &TexelSettings(),
×
390
      &ExtractSettings(),
×
391
      &CompressSettings(),
×
392
  };
393

UNCOV
394
  for (auto s : settings) {
×
395
    if (!s) {
×
396
      continue;
×
397
    }
398

UNCOV
399
    ReflectorMemberFriend member{(*s)[keyHash]};
×
400

UNCOV
401
    if (member) {
×
402
      refl = s;
403
      break;
×
404
    }
405
  }
406

407
  if (refl) {
×
408
    ReflectorMemberFriend member{(*refl)[keyHash]};
×
UNCOV
409
    ReflType rType = member;
×
410

411
    if (rType.type == REFType::Bool) {
×
412
      member = "true";
×
UNCOV
413
      return 0;
×
414
    } else {
UNCOV
415
      member = value;
×
416
      return 1;
×
417
    }
418
  } else {
419
    printerror("Invalid option: " << (key.size() > 1 ? "--" : "-") << key);
×
420
    return -1;
×
421
  }
422
}
423

UNCOV
424
void APPContext::PrintCLIHelp() const {
×
425
  printline("Options:" << std::endl);
×
426

UNCOV
427
  auto printStuff = [](auto rtti) {
×
UNCOV
428
    for (size_t i = 0; i < rtti->nTypes; i++) {
×
UNCOV
429
      if (rtti->typeAliases && rtti->typeAliases[i]) {
×
430
        es::print::Get() << "-" << rtti->typeAliases[i] << ", ";
×
431
      }
432

433
      es::print::Get() << "--" << rtti->typeNames[i];
×
434
      es::print::Get() << "  = " << rtti->typeDescs[i].part1 << std::endl;
×
435
    }
436
  };
437

UNCOV
438
  printStuff(::RTTI(MainSettings()));
×
439
  printStuff(::RTTI(TexelSettings()));
×
440

UNCOV
441
  if (ProcessFile) {
×
442
    printStuff(::RTTI(ExtractSettings()));
×
UNCOV
443
  } else if (NewArchive) {
×
UNCOV
444
    printStuff(::RTTI(CompressSettings()));
×
445
  }
446

UNCOV
447
  if (info->settings) {
×
UNCOV
448
    printStuff(RTTI());
×
449
  }
UNCOV
450
  printline("");
×
451
}
452

UNCOV
453
void DumpTypeMD(std::ostream &out, const ReflectorFriend &info,
×
454
                size_t indent = 0) {
455
  auto rtti = RTTI(info);
456

457
  auto gi = [&]() -> std::ostream & {
458
    static const char indents[]{"                "};
UNCOV
459
    return out << indents + (8 - indent) * 2;
×
460
  };
461

UNCOV
462
  for (size_t i = 0; i < rtti->nTypes; i++) {
×
UNCOV
463
    gi() << "- **" << rtti->typeNames[i] << "**\n\n";
×
UNCOV
464
    ReflectorMember member = info[i];
×
465

466
    if (member.IsReflectedSubClass()) {
×
UNCOV
467
      auto sub = member.ReflectedSubClass();
×
468
      ReflectorPureWrap subRef(sub);
469
      DumpTypeMD(
×
470
          out, static_cast<ReflectorFriend &>(static_cast<Reflector &>(subRef)),
471
          indent + 1);
472
      continue;
473
    }
474

UNCOV
475
    gi() << "  **CLI Long:** ***--" << rtti->typeNames[i] << "***\\\n";
×
476

UNCOV
477
    if (rtti->typeAliases && rtti->typeAliases[i]) {
×
478
      gi() << "  **CLI Short:** ***-" << rtti->typeAliases[i] << "***\n\n";
×
479
    }
480

481
    if (auto val = member.ReflectedValue(); !val.empty()) {
×
482
      gi() << "  **Default value:** " << val << "\n\n";
×
483
    }
484

UNCOV
485
    if (auto &rType = rtti->types[i]; rType.type == REFType::Enum) {
×
486
      auto refEnum =
UNCOV
487
          ReflectedEnum::Registry().at(JenHash(rType.asClass.typeHash));
×
UNCOV
488
      gi() << "  **Valid values:** ";
×
489

490
      if ([&] {
×
UNCOV
491
            for (size_t e = 0; e < refEnum->numMembers; e++) {
×
492
              if (refEnum->descriptions && refEnum->descriptions[e]) {
×
493
                return true;
494
              }
495
            }
496
            return false;
497
          }()) {
498
        out << "\n\n";
×
UNCOV
499
        indent++;
×
500

501
        for (size_t e = 0; e < refEnum->numMembers; e++) {
×
502
          gi() << "- " << refEnum->names[e];
×
503
          if (std::string_view desc(refEnum->descriptions[e]);
×
504
              desc.size() > 0) {
505
            size_t curLine = 0;
UNCOV
506
            size_t nextLine = desc.find('\n');
×
UNCOV
507
            out << ':';
×
508

UNCOV
509
            if (nextLine != desc.npos) {
×
UNCOV
510
              out << '\n';
×
511
              while (nextLine != desc.npos) {
×
UNCOV
512
                gi() << desc.substr(curLine, nextLine + 1 - curLine);
×
UNCOV
513
                curLine = nextLine + 1;
×
514
                nextLine = desc.find('\n', curLine);
×
515
              }
516
            } else {
517
              out << ' ' << desc << "\n\n";
×
518
            }
519

520
            out << "\n\n";
×
521
          } else {
522
            out << ", "
UNCOV
523
                << "\n\n";
×
524
          }
525
        }
UNCOV
526
        indent--;
×
527
      } else {
528
        for (size_t e = 0; e < refEnum->numMembers; e++) {
×
529
          out << refEnum->names[e] << ", ";
×
530
        }
531
      }
532

UNCOV
533
      out.seekp(out.tellp() - std::streamoff(2));
×
534
      out << "\n\n";
×
535
    }
536

537
    if (auto desc = rtti->typeDescs[i].part1; desc) {
×
538
      gi() << "  " << rtti->typeDescs[i].part1 << "\n\n";
×
539
    }
540
  }
541
}
542

543
std::string APPContext::GetClassName(pugi::xml_node node) const {
×
544
  const char *className = "[[MODULE CLASS NAME]]";
545

UNCOV
546
  if (info->settings) {
×
547
    className = RTTI()->className;
×
548
  }
549

550
  if (node) {
×
UNCOV
551
    if (auto child = node.attribute("name"); child) {
×
UNCOV
552
      className = child.as_string();
×
553
    }
554
  }
555

UNCOV
556
  return className;
×
557
}
558

559
void APPContext::GetMarkdownDoc(std::ostream &out, pugi::xml_node node) const {
×
560
  const char *description = "[[MODULE DESCRIPTION]]";
561

562
  if (node) {
×
UNCOV
563
    description = node.text().as_string();
×
564
  }
565

UNCOV
566
  out << "## " << GetClassName(node) << "\n\n### Module command: " << moduleName
×
567
      << "\n\n"
UNCOV
568
      << description << "\n\n";
×
569

UNCOV
570
  if (info->filters.size() > 0) {
×
571
    if (info->batchControlFilters.size() > 0) {
×
572
      out << "> [!NOTE]\n> The following file patterns apply to `batch.json` "
573
             "which is "
574
             "described "
575
             "[HERE](https://github.com/PredatorCZ/Spike/wiki/"
576
             "Spike---Batching)\n\n";
×
577

578
      out << "### Main file patterns: ";
×
579

UNCOV
580
      for (auto &filter : info->batchControlFilters) {
×
UNCOV
581
        out << '`' << filter << "`, ";
×
582
      }
583

584
      out.seekp(size_t(out.tellp()) - 2);
×
UNCOV
585
      out << "\n\n### Secondary file patterns: ";
×
586

587
      for (auto &filter : info->filters) {
×
UNCOV
588
        out << '`' << filter << "`, ";
×
589
      }
590

UNCOV
591
      out.seekp(size_t(out.tellp()) - 2);
×
592
    } else {
593
      out << "### Input file patterns: ";
×
594

UNCOV
595
      for (auto &filter : info->filters) {
×
596
        out << '`' << filter << "`, ";
×
597
      }
598

UNCOV
599
      out.seekp(size_t(out.tellp()) - 2);
×
600
    }
601

602
    out << "\n\n";
×
603
  }
604

605
  if (!info->settings) {
×
606
    return;
607
  }
608

UNCOV
609
  out << "### Settings\n\n";
×
610

611
  DumpTypeMD(out, Settings());
×
612
}
613

UNCOV
614
void APPContext::SetupModule() {
×
UNCOV
615
  if (mainSettings.generateLog) {
×
UNCOV
616
    CreateLog();
×
617
  }
618

UNCOV
619
  if (InitContext && !InitContext(appFolder + "data/")) {
×
UNCOV
620
    throw std::runtime_error("Error while initializing context.");
×
621
  }
622
}
623

624
using stream_type = BinWritter_t<BinCoreOpenMode::Text>;
625
static stream_type &GetStream() {
626
  static stream_type outStream;
627
  return outStream;
×
628
}
629

630
static std::ostream &GetLogger() { return GetStream().BaseStream(); }
631

632
static void printf(const char *str) { GetLogger() << str; }
633

634
const reflectorStatic *APPContext::RTTI() const { return ::RTTI(Settings()); }
×
635

636
void APPContext::CreateLog() {
×
637
  time_t curTime =
UNCOV
638
      std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
×
639
  std::tm timeStruct = *localtime(&curTime);
×
UNCOV
640
  TCHAR dateBuffer[128]{};
×
641
  const size_t dateBufferSize = sizeof(dateBuffer) / sizeof(TCHAR);
642

643
  _tcsftime(dateBuffer, dateBufferSize, _T("_%y_%m_%d-%H.%M.%S"), &timeStruct);
×
UNCOV
644
  auto logName = appFolder + moduleName + std::to_string(dateBuffer) + ".txt";
×
UNCOV
645
  GetStream().Open(logName);
×
646
  es::print::AddPrinterFunction(printf, false);
×
647

UNCOV
648
  _tcsftime(dateBuffer, dateBufferSize, _T("%c %Z"), &timeStruct);
×
649

650
  GetLogger() << "Current time: " << std::to_string(dateBuffer) << std::endl;
×
651
  GetLogger() << "Number of concurrent threads: "
UNCOV
652
              << std::thread::hardware_concurrency() << std::endl;
×
653
  GetLogger() << "Configuration:" << std::endl;
654

UNCOV
655
  auto PrintStuff = [](auto &what) {
×
UNCOV
656
    const size_t numSettings = what.NumReflectedValues();
×
657
    auto rtti = ::RTTI(what);
658

659
    for (size_t t = 0; t < numSettings; t++) {
×
UNCOV
660
      ReflectorMember member = what[t];
×
661
      std::string_view desc2;
662

UNCOV
663
      if (rtti->typeDescs && rtti->typeDescs[t].part2) {
×
664
        desc2 = rtti->typeDescs[t].part2;
665
      }
666

UNCOV
667
      ReflectorMember::KVPair pair = member.ReflectedPair();
×
668

UNCOV
669
      GetLogger() << '\t' << pair.name << ": ";
×
670

671
      if (desc2 == "HIDDEN") {
×
672
        GetLogger() << "--hidden--";
×
673
      } else {
674
        GetLogger() << pair.value;
675
      }
676

677
      GetLogger() << std::endl;
678
    }
679
  };
680

UNCOV
681
  PrintStuff(MainSettings());
×
UNCOV
682
  PrintStuff(TexelSettings());
×
UNCOV
683
  if (ProcessFile) {
×
UNCOV
684
    PrintStuff(ExtractSettings());
×
UNCOV
685
  } else if (NewArchive) {
×
686
    PrintStuff(CompressSettings());
×
687
  }
688

689
  if (info->settings) {
×
UNCOV
690
    PrintStuff(Settings());
×
691
  }
692

693
  GetLogger() << std::endl;
694
}
695

696
void GetHelp(std::ostream &str, const reflectorStatic *ref, size_t level = 1) {
×
697
  auto fillIndent = [&](size_t mod = 0) -> std::ostream & {
UNCOV
698
    for (size_t i = 0; i < level + mod; i++) {
×
UNCOV
699
      str << '\t';
×
700
    }
701

702
    return str;
703
  };
704

UNCOV
705
  for (size_t i = 0; i < ref->nTypes; i++) {
×
706
    std::string_view elName = ref->typeNames[i];
×
707
    auto elDesc = ref->typeDescs[i];
×
708
    fillIndent() << elName << std::endl;
709

UNCOV
710
    if (elDesc.part1) {
×
UNCOV
711
      fillIndent(1) << elDesc.part1 << std::endl;
×
712
    }
713

714
    auto fl = ref->types[i];
×
715

UNCOV
716
    if (fl.type == REFType::Class || fl.type == REFType::BitFieldClass) {
×
717
      GetHelp(str, reflectorStatic::Registry().at(JenHash(fl.asClass.typeHash)),
×
718
              level + 1);
719
    } else if (fl.container == REFContainer::InlineArray) {
×
720
      const auto &arr = fl.asArray;
721

722
      if (arr.type == REFType::Class || arr.type == REFType::BitFieldClass) {
×
723
        GetHelp(str,
×
UNCOV
724
                reflectorStatic::Registry().at(JenHash(arr.asClass.typeHash)),
×
725
                level + 1);
726
      }
UNCOV
727
    } else if (fl.type == REFType::Enum) {
×
UNCOV
728
      auto refEnum = ReflectedEnum::Registry().at(JenHash(fl.asClass.typeHash));
×
UNCOV
729
      fillIndent(1) << "Values: ";
×
730

731
      if ([&] {
×
732
            for (size_t e = 0; e < refEnum->numMembers; e++) {
×
733
              if (refEnum->descriptions && refEnum->descriptions[e]) {
×
734
                return true;
735
              }
736
            }
737
            return false;
738
          }()) {
739
        str << std::endl;
740

741
        for (size_t e = 0; e < refEnum->numMembers; e++) {
×
742
          fillIndent(2) << refEnum->names[e];
×
743
          if (refEnum->descriptions[e]) {
×
744
            if (std::string_view desc(refEnum->descriptions[e]);
×
745
                desc.size() > 0) {
746
              size_t curLine = 0;
UNCOV
747
              size_t nextLine = desc.find('\n');
×
748
              str << ':';
×
749

UNCOV
750
              if (nextLine != desc.npos) {
×
UNCOV
751
                str << '\n';
×
UNCOV
752
                while (nextLine != desc.npos) {
×
UNCOV
753
                  fillIndent(3) << desc.substr(curLine, nextLine + 1 - curLine);
×
UNCOV
754
                  curLine = nextLine + 1;
×
UNCOV
755
                  nextLine = desc.find('\n', curLine);
×
756
                }
757
              } else {
758
                str << ' ' << desc << std::endl;
×
759
              }
760

761
              str << std::endl;
762
            }
763
          } else {
764
            str << ", " << std::endl;
765
          }
766
        }
767
      } else {
768
        for (size_t e = 0; e < refEnum->numMembers; e++) {
×
769
          str << refEnum->names[e] << ", ";
×
770
        }
771
        str << std::endl;
772
      }
773
    }
774
  }
775
}
776

UNCOV
777
void APPContext::GetHelp(std::ostream &str) {
×
UNCOV
778
  str << moduleName << " settings." << std::endl;
×
UNCOV
779
  ::GetHelp(str, RTTI());
×
780
}
781

782
struct AppHelpContextImpl : AppHelpContext {
×
783
  std::map<std::string, std::stringstream> tagBuffers;
784

UNCOV
785
  std::ostream &GetStream(const std::string &tag) override {
×
UNCOV
786
    return tagBuffers[tag] = std::stringstream{};
×
787
  }
788
};
789

790
void APPContext::FromConfig() {
×
791
  auto configName = (appFolder + appName) + ".config";
×
792
  pugi::xml_document doc = {};
×
793

UNCOV
794
  auto TryFile = [](auto cb) {
×
795
    constexpr size_t numTries = 10;
796
    size_t curTry = 0;
797

UNCOV
798
    for (; curTry < numTries; curTry++) {
×
799
      try {
800
        cb();
×
801
      } catch (const es::FileNotFoundError &) {
×
UNCOV
802
      } catch (const es::FileInvalidAccessError &) {
×
UNCOV
803
        std::this_thread::sleep_for(std::chrono::milliseconds(5));
×
804
        continue;
805
      }
806

807
      break;
808
    }
809

UNCOV
810
    if (curTry == numTries) {
×
811
      throw std::runtime_error("Cannot access config. File is locked.");
×
812
    }
813
  };
UNCOV
814

×
815
  TryFile([&] {
816
    auto flags = XMLDefaultParseFlags;
817
    flags += XMLParseFlag::Comments;
UNCOV
818
    doc = XMLFromFile(configName, flags);
×
819
    ReflectorXMLUtil::Load(MainSettings(), doc.child("common"));
820

821
    if (info->settings) {
×
UNCOV
822
      ReflectorXMLUtil::Load(Settings(), doc.child(moduleName));
×
UNCOV
823
    }
×
824
  });
825

826
  {
827
    {
828
      AppHelpContextImpl helpCtx;
829

830
      if (auto commentNode = doc.find_child([](pugi::xml_node &node) {
×
831
            return node.type() == pugi::xml_node_type::node_comment &&
×
832
                   std::string_view(node.value()).starts_with("common");
833
          });
UNCOV
834
          commentNode) {
×
835
        std::string_view comment(commentNode.value());
836
        std::string_view lastTag;
837
        size_t lastPos = 0;
UNCOV
838

×
839
        while (true) {
840
          lastPos = comment.find("<-tag:", lastPos);
×
841

×
UNCOV
842
          if (!lastTag.empty()) {
×
UNCOV
843
            std::string tagName(lastTag);
×
844
            lastTag = {lastTag.begin(), comment.end()};
845
            size_t dataBegin = lastTag.find_first_of('\n');
846

847
            if (dataBegin != lastTag.npos) {
848
              dataBegin++;
849
              const size_t dataEnd =
UNCOV
850
                  lastPos != lastTag.npos ? lastPos : lastTag.size();
×
851
              helpCtx.GetStream(tagName)
×
852
                  << lastTag.substr(dataBegin, dataEnd - dataBegin);
853
            }
854

UNCOV
855
            lastTag = {};
×
856
          }
857

UNCOV
858
          if (lastPos == comment.npos) {
×
UNCOV
859
            break;
×
860
          }
861

×
862
          const size_t tagBegin = lastPos += 6;
×
863

864
          lastPos = comment.find("->", lastPos);
×
865

866
          if (lastPos == comment.npos) {
867
            break;
868
          }
869

UNCOV
870
          auto tagName = comment.substr(tagBegin, lastPos - tagBegin);
×
UNCOV
871
          lastTag = es::TrimWhitespace(tagName);
×
872
        }
×
873

874
        doc.remove_child(commentNode);
×
UNCOV
875
      }
×
UNCOV
876

×
877
      std::stringstream str;
878
      str << "common settings." << std::endl;
879
      ::GetHelp(str, ::RTTI(MainSettings()));
880
      AdditionalHelp(&helpCtx, 1);
881

882
      for (auto &[tag, data] : helpCtx.tagBuffers) {
×
UNCOV
883
        str << "\t<-tag: " << tag << "->\n" << data.str();
×
UNCOV
884
      }
×
885

886
      auto buff = std::move(str).str();
UNCOV
887
      pugi::xml_node commonNode;
×
888
      auto commentNode = doc.append_child(pugi::node_comment);
×
889
      commentNode.set_value(buff.data());
UNCOV
890

×
UNCOV
891
      if (commonNode = doc.child("common"); commonNode) {
×
892
        doc.insert_move_after(commonNode, commentNode);
×
893
      } else {
894
        commonNode = doc.append_child("common");
UNCOV
895
      }
×
896
      ReflectorXMLUtil::Save(MainSettings(), commonNode,
897
                             ReflectorXMLUtil::Flags_StringAsAttribute);
UNCOV
898
    }
×
899

900
    if (info->settings) {
901
      if (auto commentNode = doc.find_child([&](pugi::xml_node &node) {
UNCOV
902
            return node.type() == pugi::xml_node_type::node_comment &&
×
903
                   std::string_view(node.value()).starts_with(moduleName);
904
          });
905
          commentNode) {
UNCOV
906
        doc.remove_child(commentNode);
×
907
      }
908

909
      std::stringstream str;
UNCOV
910
      GetHelp(str);
×
UNCOV
911
      auto buff = std::move(str).str();
×
912
      pugi::xml_node node;
913
      auto commentNode = doc.append_child(pugi::node_comment);
UNCOV
914
      commentNode.set_value(buff.data());
×
915

916
      if (node = doc.child(moduleName); node) {
917
        doc.insert_move_after(node, commentNode);
×
918
      } else {
919
        node = doc.append_child(moduleName);
×
920
      }
921
      ReflectorXMLUtil::Save(Settings(), node,
922
                             {ReflectorXMLUtil::Flags_StringAsAttribute});
×
UNCOV
923
    }
×
924

925
    TryFile([&] {
926
      XMLToFile(configName, doc,
UNCOV
927
                {XMLFormatFlag::WriteBOM, XMLFormatFlag::IndentAttributes});
×
UNCOV
928
    });
×
UNCOV
929
  }
×
930
}
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