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

PredatorCZ / PreCore / 577

21 Apr 2026 05:37PM UTC coverage: 51.747% (-0.01%) from 51.76%
577

push

github

PredatorCZ
fix face indexing for DDS

0 of 2 new or added lines in 1 file covered. (0.0%)

80 existing lines in 1 file now uncovered.

4131 of 7983 relevant lines covered (51.75%)

11425.98 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 es::RuntimeError("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 es::ImplementationError("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=;
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);
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

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

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

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

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(),
×
388
      &CliSettings(),
×
389
      &TexelSettings(),
×
390
      &ExtractSettings(),
×
391
      &CompressSettings(),
×
392
  };
393

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

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

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

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

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

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

427
  auto printStuff = [](auto rtti) {
×
428
    for (size_t i = 0; i < rtti->nTypes; i++) {
×
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

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

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

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

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[]{"                "};
459
    return out << indents + (8 - indent) * 2;
×
460
  };
461

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

466
    if (member.IsReflectedSubClass()) {
×
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

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

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

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

490
      if ([&] {
×
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";
×
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;
506
            size_t nextLine = desc.find('\n');
×
507
            out << ':';
×
508

509
            if (nextLine != desc.npos) {
×
510
              out << '\n';
×
511
              while (nextLine != desc.npos) {
×
512
                gi() << desc.substr(curLine, nextLine + 1 - curLine);
×
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 << ", "
523
                << "\n\n";
×
524
          }
525
        }
526
        indent--;
×
527
      } else {
528
        for (size_t e = 0; e < refEnum->numMembers; e++) {
×
529
          out << refEnum->names[e] << ", ";
×
530
        }
531
      }
532

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

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

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

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) {
×
563
    description = node.text().as_string();
×
564
  }
565

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

703
    return str;
704
  };
705

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

808
      break;
809
    }
810

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

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

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

827
  {
828
    {
829
      AppHelpContextImpl helpCtx;
830

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

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

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

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

UNCOV
856
            lastTag = {};
×
857
          }
858

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

×
UNCOV
863
          const size_t tagBegin = lastPos += 6;
×
864

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

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

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

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

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

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

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

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

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

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

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

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