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

PredatorCZ / PreCore / 566

13 Nov 2025 10:50PM UTC coverage: 52.3% (-0.01%) from 52.313%
566

push

github

PredatorCZ
fix output folder for extract context case

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

4 existing lines in 1 file now uncovered.

4128 of 7893 relevant lines covered (52.3%)

10507.73 hits per line

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

10.13
/src/app/in_context.cpp
1
/*  Spike is universal dedicated module handler
2
    This source contains context for input data
3

4
    Copyright 2021-2023 Lukas Cone
5

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

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

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

19
#include "spike/app/context.hpp"
20
#include "spike/app/out_context.hpp"
21
#include "spike/app/texel.hpp"
22
#include "spike/app/tmp_storage.hpp"
23
#include "spike/format/ZIP_istream.inl"
24
#include "spike/io/binreader.hpp"
25
#include "spike/io/binwritter.hpp"
26
#include "spike/io/directory_scanner.hpp"
27
#include "spike/io/fileinfo.hpp"
28
#include "spike/io/stat.hpp"
29
#include "spike/master_printer.hpp"
30
#include <list>
31
#include <mutex>
32
#include <optional>
33
#include <spanstream>
34

35
static std::mutex simpleIOLock;
36

37
const std::vector<std::string> &AppContextShare::SupplementalFiles() {
×
38
  if (!supplementals) {
×
39
    throw std::runtime_error(
×
40
        "Invalid call of SupplementalFiles, module is not for batch.");
×
41
  }
42

43
  return supplementals.value();
×
44
}
45

46
const std::vector<std::string> &ZIPIOContext::SupplementalFiles() {
×
47
  if (!supplementals) {
×
48
    throw std::runtime_error(
×
49
        "Invalid call of SupplementalFiles, module is not for batch.");
×
50
  }
51

52
  return supplementals.value();
×
53
}
54

55
struct AppContextShareImpl : AppContextShare {
56
  NewTexelContext *NewImage(NewTexelContextCreate ctx,
56✔
57
                            const std::string *path) override {
58
    if (texelContext) {
56✔
59
      texelContext->Finish();
28✔
60
    }
61

62
    texelContext = CreateTexelContext(ctx, this);
56✔
63

64
    if (path) {
56✔
65
      texelContext->pathOverride.Load(*path);
28✔
66
    }
67

68
    return texelContext.get();
56✔
69
  }
70

71
  std::pair<std::string, size_t> IdealPath(const std::string &path) {
190✔
72
    std::string filePath;
73
    size_t delimeter = 0;
74

75
    if (basePathParts.empty()) {
190✔
76
      filePath = std::string(basePath.GetFullPath()) + path;
380✔
77
      delimeter = basePath.GetFullPath().size();
78
    } else {
79
      std::string pathCopy(path);
80

81
      if (substPath.size() && path.starts_with(substPath)) {
×
82
        pathCopy.erase(0, substPath.size());
×
83
      }
84
      AFileInfo pathInfo(pathCopy);
×
85
      auto exploded = pathInfo.Explode();
×
86
      const size_t numItems = std::min(exploded.size(), basePathParts.size());
×
87

88
      if (basePath.GetFullPath().at(0) == '/') {
×
89
        filePath.push_back('/');
×
90
      }
91

92
      size_t i = 0;
93

94
      for (; i < numItems; i++) {
×
95
        if (basePathParts[i] == exploded[i]) {
×
96
          filePath.append(exploded[i]);
97
          filePath.push_back('/');
×
98
          continue;
99
        }
100

101
        break;
102
      }
103

104
      for (size_t j = i; j < basePathParts.size(); j++) {
×
105
        filePath.append(basePathParts[j]);
106
        filePath.push_back('/');
×
107
      }
108

109
      delimeter = filePath.size();
110

111
      for (; i < exploded.size(); i++) {
×
112
        filePath.append(exploded[i]);
113
        filePath.push_back('/');
×
114
      }
115

116
      filePath.pop_back();
117
    }
118

119
    return {filePath, delimeter};
190✔
120
  }
121

122
  NewFileContext NewFile(const std::string &path) override {
190✔
123
    const auto [filePath, delimeter] = IdealPath(path);
190✔
124

125
    try {
126
      outFile = BinWritter(filePath);
379✔
127
    } catch (const es::FileInvalidAccessError &e) {
1✔
128
      mkdirs(filePath);
1✔
129
      outFile = BinWritter(filePath);
1✔
130
    }
1✔
131

132
    if (forEachFile) {
190✔
133
      forEachFile();
134
    }
135

136
    return {outFile.BaseStream(), filePath, delimeter};
190✔
137
  }
380✔
138

139
  void BaseOutputPath(std::string basePath_) override {
×
140
    if (size_t found = basePath_.find_first_of(':'); found != basePath_.npos) {
×
141
      substPath = basePath_.substr(0, found);
×
142
      basePath_.erase(0, found + 1);
×
143
    }
144

145
    if (basePath_.empty()) {
×
146
      throw std::runtime_error("Output path cannot be empty");
×
147
    }
148

149
    if (basePath_.back() != '/') {
×
150
      basePath_.push_back('/');
×
151
    }
152
    basePath = AFileInfo(basePath_);
×
153
    basePathParts = basePath.Explode();
×
154
  }
155

156
  JenHash Hash() override { return JenHash(FullPath()); }
×
157

158
  std::string FullPath() override {
×
159
    return std::string(basePath.GetFullPath()) +
×
160
           std::string(workingFile.GetFullPath());
×
161
  }
162

163
  ~AppContextShareImpl() {
28✔
164
    if (texelContext) {
28✔
165
      texelContext->Finish();
28✔
166
    }
167
  }
56✔
168

×
169
  BinWritter outFile;
170
  AFileInfo basePath;
171
  std::string substPath;
172
  std::vector<std::string_view> basePathParts;
173
  std::unique_ptr<NewTexelContextImpl> texelContext;
28✔
174
};
28✔
175

28✔
176
struct SimpleIOContext : AppContextShareImpl {
177
  SimpleIOContext(const std::string &path,
56✔
178
                  std::optional<std::vector<std::string>> supplementals_) {
179
    mainFile.Open(path);
180
    workingFile.Load(path);
181
    supplementals = std::move(supplementals_);
182

183
    if (!cliSettings.out.empty()) {
184
      BaseOutputPath(cliSettings.out);
185
    }
186
  }
187
  std::istream *OpenFile(const std::string &path);
28✔
188

924✔
189
  AppContextStream RequestFile(const std::string &path) override;
28✔
190

28✔
191
  AppContextFoundStream FindFile(const std::string &rootFolder,
192
                                 const std::string &pattern) override;
193
  std::istream &GetStream() override;
28✔
194
  std::string GetBuffer(size_t size, size_t begin) override;
×
195

196
  void DisposeFile(std::istream *str) override;
28✔
197

198
  AppExtractContext *ExtractContext(std::string_view name) override {
199
    if (ectx) [[unlikely]] {
200
      return ectx.get();
201
    }
202

203
    std::string arcPath(workingFile.GetFolder());
204
    arcPath.append(name);
205

206
    auto [outPath, delimeter] = IdealPath(arcPath);
207

208
    if (mainSettings.extractSettings.makeZIP) {
×
209
      if (workingFile.GetExtension() == ".zip") {
×
210
        outPath.append("_out");
×
211
      }
212

213
      outPath.append(".zip");
×
214

215
      auto uniq = std::make_unique<ZIPExtactContext>(outPath);
216
      uniq->forEachFile = forEachFile;
×
217
      ectx = std::move(uniq);
218
    } else {
×
219
      if (!mainSettings.extractSettings.folderPerArc) {
×
NEW
220
        outPath = AFileInfo(outPath).GetFolder();
×
221
      } else {
222
        outPath.push_back('/');
223
      }
×
224

NEW
225
      mkdirs(outPath);
×
UNCOV
226
      auto uniq = std::make_unique<IOExtractContext>(outPath);
×
227
      uniq->forEachFile = forEachFile;
228
      ectx = std::move(uniq);
229
    }
×
UNCOV
230

×
231
    return ectx.get();
UNCOV
232
  }
×
233

234
  AppExtractContext *ExtractContext() override {
UNCOV
235
    return ExtractContext(workingFile.GetFilename());
×
236
  }
×
237

×
238
  void Finish() override {
239
    if (ectx && mainSettings.extractSettings.makeZIP) {
240
      static_cast<ZIPExtactContext *>(ectx.get())->FinishZIP([] {
241
        printinfo("Generating cache.");
242
      });
243
    }
244
  }
×
245

×
246
private:
247
  BinReader mainFile;
248
  BinReader streamedFiles[32];
×
249
  uint32 usedFiles = 0;
×
250
  std::unique_ptr<AppExtractContext> ectx;
251
};
×
252

×
253
std::istream *SimpleIOContext::OpenFile(const std::string &path) {
254
  std::lock_guard<std::mutex> guard(simpleIOLock);
255
  for (size_t b = 0; b < 32; b++) {
256
    uint32 bit = 1 << b;
257
    if (!(usedFiles & bit)) {
258
      streamedFiles[b].Open(path);
259
      usedFiles ^= bit;
260
      return &streamedFiles[b].BaseStream();
261
    }
262
  }
263

×
264
  throw std::out_of_range("Maximum opened files reached!");
265
}
×
266

×
267
AppContextStream SimpleIOContext::RequestFile(const std::string &path) {
×
268
  AFileInfo wFile(workingFile);
×
269
  AFileInfo pFile(path);
×
270
  auto catchedFile = pFile.CatchBranch(wFile.GetFolder());
×
271
  return {OpenFile(catchedFile), this};
272
}
273

274
AppContextFoundStream SimpleIOContext::FindFile(const std::string &rootFolder,
×
275
                                                const std::string &pattern) {
276
  DirectoryScanner sc;
277
  sc.AddFilter(pattern);
×
278
  sc.Scan(rootFolder);
279

×
280
  if (sc.Files().empty()) {
×
281
    throw es::FileNotFoundError(pattern);
×
282
  } else if (sc.Files().size() > 1) {
283
    std::string *winner = nullptr;
284
    size_t minFolder = 0x10000;
×
285
    size_t minLevel = 0x10000;
286

×
287
    for (auto &f : sc) {
×
288
      size_t foundIdx = f.find_last_of('/');
×
289

290
      if (foundIdx == f.npos) {
×
291
        throw std::runtime_error("Too many files found for pattern: " +
×
292
                                 pattern);
×
293
      }
294

295
      if (foundIdx < minFolder) {
296
        winner = &f;
297
        minFolder = foundIdx;
×
298
        minLevel = std::count(f.begin(), f.end(), '/');
299
      } else if (foundIdx == minFolder) {
300
        if (size_t clevel = std::count(f.begin(), f.end(), '/');
×
301
            clevel < minLevel) {
×
302
          winner = &f;
×
303
          minFolder = foundIdx;
304
          minLevel = clevel;
305
        } else if (clevel == minLevel) {
×
306
          throw std::runtime_error("Too many files found for pattern: " +
307
                                   pattern);
308
        }
×
309
      }
×
310
    }
×
311

312
    if (!winner) {
313
      throw std::runtime_error("Too many files found for pattern: " + pattern);
314
    }
315

×
316
    return {OpenFile(*winner), this, AFileInfo(*winner)};
×
317
  }
×
318

319
  return {OpenFile(sc.Files().front()), this, AFileInfo(sc.Files().front())};
320
}
321

322
void SimpleIOContext::DisposeFile(std::istream *str) {
×
323
  size_t index = 0;
×
324

325
  for (auto &f : streamedFiles) {
326
    if (&f.BaseStream() == str) {
×
327
      uint32 bit = 1 << index;
328

329
      if (!(usedFiles & bit)) {
×
330
        throw std::runtime_error("Stream already freed.");
331
      }
332

×
333
      es::Dispose(f);
334
      usedFiles ^= bit;
335
      return;
×
336
    }
×
337

×
338
    index++;
339
  }
×
340

×
341
  throw std::runtime_error("Requested stream not found!");
342
}
343

×
344
std::istream &SimpleIOContext::GetStream() { return mainFile.BaseStream(); }
×
345

×
346
std::string SimpleIOContext::GetBuffer(size_t size, size_t begin) {
347
  mainFile.Push();
348
  mainFile.Seek(begin);
×
349
  std::string buffer;
350
  mainFile.ReadContainer(
351
      buffer, size == size_t(-1) ? (mainFile.GetSize() - begin) : size);
×
352
  mainFile.Pop();
353

354
  return buffer;
28✔
355
}
356

×
357
std::shared_ptr<AppContextShare>
×
358
MakeIOContext(const std::string &path,
359
              std::optional<std::vector<std::string>> supplementals) {
360
  return std::make_unique<SimpleIOContext>(path, supplementals);
×
361
}
×
362

363
struct ZIPIOContextInstance : AppContextShareImpl {
364
  ZIPIOContextInstance(const ZIPIOContextInstance &) = delete;
×
365
  ZIPIOContextInstance(ZIPIOContextInstance &&) = delete;
366
  ZIPIOContextInstance(ZIPIOContext *base_, ZIPIOEntry entry_)
367
      : base(base_), entry(entry_) {
368
    workingFile.Load(entry_.AsView());
28✔
369
    BaseOutputPath(base->basePath);
370
  }
56✔
371

372
  ~ZIPIOContextInstance() {
373
    if (stream) {
374
      base->DisposeFile(stream);
375
    }
376
  }
×
377

×
378
  AppContextStream RequestFile(const std::string &path) override {
×
379
    return base->RequestFile(path);
×
380
  }
381
  void DisposeFile(std::istream *file) override { base->DisposeFile(file); }
382
  AppContextFoundStream FindFile(const std::string &rootFolder,
×
383
                                 const std::string &pattern) override {
×
384
    return base->FindFile(rootFolder, pattern);
×
385
  }
386

387
  std::istream &GetStream() override {
×
388
    if (!stream) {
389
      stream = base->OpenFile(entry);
390
    }
391
    return *stream;
392
  }
×
393
  std::string GetBuffer(size_t size, size_t begin) override {
×
394
    BinReaderRef rd(GetStream());
×
395
    rd.Push();
396
    rd.Seek(begin);
397
    std::string buffer;
398
    rd.ReadContainer(buffer,
×
399
                     size == size_t(-1) ? (rd.GetSize() - begin) : size);
×
400
    rd.Pop();
401
    return buffer;
×
402
  }
×
403

404
  AppExtractContext *ExtractContext(std::string_view name) override {
×
405
    if (ectx) [[unlikely]] {
406
      return ectx.get();
407
    }
×
408

×
409
    if (mainSettings.extractSettings.makeZIP) {
×
410
      base->InitMerger();
411
      entriesPath = RequestTempFile();
×
412
      auto uniq = std::make_unique<ZIPExtactContext>(entriesPath, false);
413
      if (mainSettings.extractSettings.folderPerArc) {
×
414
        uniq->prefixPath = workingFile.GetFolder();
×
415
        uniq->prefixPath += name;
416
        uniq->prefixPath.push_back('/');
417
      }
418
      uniq->forEachFile = forEachFile;
×
419
      ectx = std::move(uniq);
×
420
    } else {
421
      std::string outPath;
×
422
      if (!mainSettings.extractSettings.folderPerArc) {
423
        outPath = workingFile.GetFolder();
424
      } else {
×
425
        outPath = std::string(basePath.GetFullPath());
×
426
        outPath += workingFile.GetFolder();
×
427
        outPath += name;
428
        mkdirs(outPath);
429
        outPath.push_back('/');
×
430
      }
×
431

×
432
      auto uniq = std::make_unique<IOExtractContext>(outPath);
×
433
      uniq->forEachFile = forEachFile;
×
434
      ectx = std::move(uniq);
×
435
    }
436

×
437
    return ectx.get();
438
  }
×
439

440
  AppExtractContext *ExtractContext() override {
441
    return ExtractContext(workingFile.GetFilename());
442
  }
×
443

444
  void Finish() override {
445
    if (ectx && mainSettings.extractSettings.makeZIP) {
×
446
      base->Merge(static_cast<ZIPExtactContext *>(ectx.get()), entriesPath);
447
      es::Dispose(ectx);
448
      es::RemoveFile(entriesPath);
×
449
    }
×
450
  }
451

452
  ZIPIOContext *base;
×
453
  std::istream *stream = nullptr;
×
454
  ZipEntry entry;
455
  std::unique_ptr<AppExtractContext> ectx;
456
  std::string entriesPath;
457
};
×
458

459
std::shared_ptr<AppContextShare> ZIPIOContext::Instance(ZIPIOEntry entry) {
460
  return std::make_unique<ZIPIOContextInstance>(this, entry);
×
461
}
×
462

463
static std::mutex ZIPLock;
464

×
465
struct ZIPDataHolder {
×
466
  virtual ~ZIPDataHolder() = default;
×
467
};
468

×
469
struct ZIPIOContext_implbase : ZIPIOContext {
470
  ZIPIOContext_implbase(const std::string &file) : zipMount(file) {}
471
  std::istream *OpenFile(const ZipEntry &entry) override;
472
  std::string GetChunk(const ZipEntry &entry, size_t offset,
473
                       size_t size) const override;
474
  void DisposeFile(std::istream *str) override;
475

476
  void Merge(ZIPExtactContext *eCtx, const std::string &records) override {
477
    std::lock_guard<std::mutex> lg(mergerMtx);
478
    merger->Merge(*eCtx, records);
479
  }
×
480

×
481
  void InitMerger() override {
482
    std::lock_guard<std::mutex> lg(mergerMtx);
483
    if (!merger) {
484
      merger.emplace(basePath + "_out.zip", RequestTempFile());
485
    }
486
  }
487

488
  void Finish() override {
489
    if (merger) {
490
      merger->FinishMerge([] { printinfo("Generating cache."); });
×
491
    }
492
  }
493

494
protected:
495
  std::list<std::spanstream> openedFiles;
496
  es::MappedFile zipMount;
×
497
  std::optional<ZIPMerger> merger;
×
498
  std::mutex mergerMtx;
×
499
};
500

501
struct ZIPMemoryStream : ZIPDataHolder {
×
502
  std::istringstream stream;
×
503

×
504
  ZIPMemoryStream(std::string &&input)
×
505
      : stream(input, std::ios::in | std::ios::binary) {}
506
};
507

508
struct ZIPFileStream : ZIPDataHolder {
×
509
  BinReader rd;
×
510
  std::string path;
×
511
  ZIPFileStream(const std::string &path_) : rd(path_), path(path_) {}
512
  ~ZIPFileStream() {
513
    es::Dispose(rd);
514
    try {
515
      es::RemoveFile(path);
516
    } catch (const std::exception &e) {
517
      printerror(e.what());
518
    }
519
  }
520
};
521

522
std::istream *ZIPIOContext_implbase::OpenFile(const ZipEntry &entry) {
523
  auto dataBegin = static_cast<char *>(zipMount.data) + entry.offset;
524
  auto dataEnd = dataBegin + entry.size;
525

526
  std::lock_guard<std::mutex> guard(ZIPLock);
527
  auto &str = openedFiles.emplace_back(std::span<char>(dataBegin, dataEnd),
528
                                       std::ios::binary | std::ios::in);
529
  return &str;
530
}
531

532
std::string ZIPIOContext_implbase::GetChunk(const ZipEntry &entry,
533
                                            size_t offset, size_t size) const {
534
  auto dataBegin = static_cast<char *>(zipMount.data) + entry.offset + offset;
535
  auto dataEnd = dataBegin + size;
536

537
  return {dataBegin, dataEnd};
538
}
539

540
void ZIPIOContext_implbase::DisposeFile(std::istream *str) {
541
  std::lock_guard<std::mutex> guard(ZIPLock);
542
  openedFiles.remove_if([&](auto &spanStr) {
×
543
    return static_cast<std::istream *>(&spanStr) == str;
×
544
  });
×
545
}
546

547
struct ZIPIOContextIter_impl : ZIPIOEntryRawIterator {
×
548
  using map_type = std::map<std::string_view, ZipEntry>;
×
549
  ZIPIOContextIter_impl(const map_type &map)
×
550
      : base(&map), current(map.begin()), end(map.end()) {}
551
  ZIPIOEntry Fist() const override {
552
    if (current == end) {
×
553
      return {};
554
    }
×
555
    return {current->second, current->first};
×
556
  }
557
  ZIPIOEntry Next() const override {
×
558
    if (current == end) {
559
      return {};
560
    }
×
561
    current++;
562
    if (current == end) {
×
563
      return {};
×
564
    }
565

566
    return {current->second, current->first};
567
  }
568
  size_t Count() const override { return base->size(); }
569

570
  const map_type *base;
×
571
  mutable map_type::const_iterator current;
×
572
  map_type::const_iterator end;
×
573
};
×
574

575
struct ZIPIOContext_impl : ZIPIOContext_implbase {
576
  AppContextStream RequestFile(const std::string &path) override;
577

×
578
  AppContextFoundStream FindFile(const std::string &rootFolder,
×
579
                                 const std::string &pattern) override;
×
580

581
  ZIPIOContextIterator Iter(ZIPIOEntryType) const override {
582
    return {std::make_unique<ZIPIOContextIter_impl>(vfs)};
×
583
  }
×
584

585
  ZIPIOContext_impl(const std::string &file, const PathFilter &pathFilter_,
586
                    const PathFilter &moduleFilter_)
587
      : ZIPIOContext_implbase(file), pathFilter(&pathFilter_),
588
        moduleFilter(&moduleFilter_) {
×
589
    Read();
590
    pathFilter = moduleFilter = nullptr;
591
  }
592

593
  ZIPIOContext_impl(const std::string &file) : ZIPIOContext_implbase(file) {
594
    Read();
595
  }
596

597
private:
598
  void Read();
599
  const PathFilter *pathFilter = nullptr;
600
  const PathFilter *moduleFilter = nullptr;
601
  std::map<std::string_view, ZipEntry> vfs;
×
602
};
×
603

604
AppContextStream ZIPIOContext_impl::RequestFile(const std::string &path) {
605
  auto found = vfs.find(path);
×
606

607
  if (es::IsEnd(vfs, found)) {
×
608
    throw es::FileNotFoundError(path);
×
609
  }
×
610

×
611
  return {OpenFile(found->second), this};
612
}
613

×
614
AppContextFoundStream ZIPIOContext_impl::FindFile(const std::string &,
×
615
                                                  const std::string &pattern) {
616
  PathFilter filter;
617
  filter.AddFilter(pattern);
618

619
  for (auto &f : vfs) {
620
    std::string_view kvi(f.first);
621
    size_t lastSlash = kvi.find_last_of('/');
622
    kvi.remove_prefix(lastSlash + 1);
623

624
    if (filter.IsFiltered(kvi)) {
×
625
      return {OpenFile(f.second), this, AFileInfo(f.first)};
×
626
    }
627
  }
×
628

×
629
  throw es::FileNotFoundError(pattern);
630
}
631

×
632
// Warning: Unaligned accesses
633
// Note: Multiple central directories? (unlikely)
634
void ZIPIOContext_impl::Read() {
×
635
  auto curEnd = static_cast<char *>(zipMount.data) + zipMount.fileSize -
636
                (sizeof(ZIPCentralDir) - 2);
×
637
  auto curLocator = reinterpret_cast<const ZIPCentralDir *>(curEnd);
×
638

639
  if (curLocator->id != ZIPCentralDir::ID) {
×
640
    int numIters = 4096;
×
641
    while (numIters > 0) {
642
      curEnd--;
×
643
      numIters--;
644

×
645
      curLocator = reinterpret_cast<const ZIPCentralDir *>(curEnd);
×
646
      if (curLocator->id == ZIPCentralDir::ID) {
647
        break;
648
      }
649
    }
×
650
  }
651

652
  if (curLocator->id != ZIPCentralDir::ID) {
653
    throw std::runtime_error("Cannot find ZIP central directory");
654
  }
×
655

×
656
  uint64 dirOffset = 0;
657
  uint64 numEntries = 0;
658
  uint64 dirSize = 0;
659

×
660
  if (curLocator->dirOffset == -1U || curLocator->numDirEntries == uint16(-1) ||
661
      curLocator->dirSize == -1U) {
×
662
    curEnd -= sizeof(ZIP64CentralDir) - 8;
×
663
    auto curLocatorX64 = reinterpret_cast<const ZIP64CentralDir *>(curEnd);
×
664
    if (curLocatorX64->id != ZIP64CentralDir::ID) {
665
      int numIters = 4096;
666
      while (numIters > 0) {
×
667
        curEnd--;
668
        numIters--;
669

670
        curLocatorX64 = reinterpret_cast<const ZIP64CentralDir *>(curEnd);
671
        if (curLocatorX64->id == ZIP64CentralDir::ID) {
672
          break;
×
673
        }
×
674
      }
675
    }
676

677
    if (curLocatorX64->id != ZIP64CentralDir::ID) {
678
      throw std::runtime_error("Cannot find ZIPx64 central directory");
679
    }
680

×
681
    std::spanstream entriesSpan(
×
682
        std::span<char>(curEnd,
×
683
                        static_cast<char *>(zipMount.data) + zipMount.fileSize),
684
        std::ios::binary | std::ios::in);
×
685
    BinReaderRef rd(entriesSpan);
686
    ZIP64CentralDir x64CentraDir;
×
687
    rd.Read(x64CentraDir);
×
688

×
689
    dirOffset = x64CentraDir.dirOffset;
690
    numEntries = x64CentraDir.numDirEntries;
691
    dirSize = x64CentraDir.dirSize;
×
692
  } else {
693
    dirOffset = curLocator->dirOffset;
694
    numEntries = curLocator->numDirEntries;
695
    dirSize = curLocator->dirSize;
696
  }
697

×
698
  auto entriesBegin = static_cast<char *>(zipMount.data) + dirOffset;
×
699
  auto entriesEnd = entriesBegin + dirSize;
700
  std::spanstream entriesSpan(std::span<char>(entriesBegin, entriesEnd),
701
                              std::ios::binary | std::ios::in);
702
  BinReaderRef rd(entriesSpan);
703
  std::spanstream localStreamSpan(
704
      std::span<char>(static_cast<char *>(zipMount.data), entriesBegin),
×
705
      std::ios::binary | std::ios::in);
706
  BinReaderRef localRd(localStreamSpan);
707

708
  for (size_t d = 0; d < numEntries; d++) {
709
    uint32 id;
×
710
    rd.Push();
×
711
    rd.Read(id);
×
712
    rd.Pop();
×
713

×
714
    switch (id) {
×
715
    case ZIPFile::ID: {
×
716
      ZIPFile hdr;
717
      rd.Read(hdr);
718

×
719
      [&] {
720
        std::string_view entryName(entriesBegin + rd.Tell(), hdr.fileNameSize);
721
        rd.Skip(hdr.fileNameSize);
×
722

723
        if (!hdr.compressedSize) {
724
          return;
×
725
        }
×
726

727
        if (hdr.flags[ZIPLocalFlag::Encrypted]) {
728
          throw std::runtime_error("ZIP cannot have encrypted files!");
×
729
        }
730

731
        if (hdr.compression != ZIPCompressionMethod::Store) {
732
          throw std::runtime_error("ZIP cannot have compressed files!");
733
        }
734

×
735
        if (!hdr.fileNameSize) {
736
          throw std::runtime_error("ZIP local file's path must be specified!");
737
        }
×
738

739
        size_t entrySize = hdr.compressedSize;
×
740
        size_t localOffset = hdr.localHeaderOffset;
×
741

×
742
        if (hdr.compressedSize == -1U || hdr.uncompressedSize == -1U ||
743
            hdr.localHeaderOffset == -1U) {
×
744
          const size_t extraEnd = rd.Push() + hdr.extraFieldSize;
×
745

746
          while (rd.Tell() < extraEnd) {
747
            ZIP64Extra extra;
×
748
            rd.Read(extra.id);
×
749
            rd.Read(extra.size);
750

751
            if (extra.id == 1) {
×
752
              if (hdr.uncompressedSize == -1U) {
×
753
                rd.Read(extra.uncompressedSize);
754
                entrySize = extra.uncompressedSize;
755
              }
×
756
              if (hdr.compressedSize == -1U) {
×
757
                rd.Read(extra.compressedSize);
758
              }
759
              if (hdr.localHeaderOffset == -1U) {
×
760
                rd.Read(extra.localHeaderOffset);
×
761
                localOffset = extra.localHeaderOffset;
762
              }
×
763
              break;
764
            } else {
×
765
              rd.Skip(extra.size);
766
            }
×
767
          }
768

769
          rd.Pop();
770
        }
771

×
772
        if (pathFilter && !pathFilter->IsFiltered(entryName)) {
×
773
          return;
774
        }
×
775

776
        if (moduleFilter && !moduleFilter->IsFiltered(entryName)) {
×
777
          return;
778
        }
779

×
780
        ZIPLocalFile localEntry;
781
        localRd.Seek(localOffset);
×
782
        localRd.Read(localEntry);
783
        ZipEntry entry;
×
784
        entry.size = entrySize;
785
        entry.offset = localRd.Tell() + localEntry.extraFieldSize +
×
786
                       localEntry.fileNameSize;
787
        vfs.emplace(entryName, entry);
788
      }();
789

790
      rd.Skip(hdr.extraFieldSize + hdr.fileCommentSize);
791
      break;
792
    }
×
793
    default:
794
      using std::to_string;
795
      throw std::runtime_error("Invalid dir entry " + to_string(id) + " at " +
796
                               to_string(rd.Tell() + dirOffset));
×
797
    }
798
  }
799
}
800

801
ZIPIOEntry::operator bool() const {
×
802
  return std::visit([](auto &name) { return !name.empty(); }, name);
×
803
}
804

×
805
struct ZIPIOContextCached : ZIPIOContext_implbase {
×
806
  AppContextStream RequestFile(const std::string &path) override {
×
807

×
808
    auto found = cache.RequestFile(path);
×
809

810
    if (!found.size) {
×
811
      throw es::FileNotFoundError(path);
812
    }
813

×
814
    return {OpenFile(found), this};
815
  }
×
816

×
817
  AppContextFoundStream FindFile(const std::string &,
818
                                 const std::string &pattern) override {
819
    auto found = cache.FindFile(pattern);
820

821
    if (!found.size) {
×
822
      throw es::FileNotFoundError(pattern);
×
823
    }
824

825
    return std::visit(
826
        [&](auto &item) -> AppContextFoundStream {
×
827
          return {OpenFile(found), this, AFileInfo(item)};
828
        },
×
829
        found.name);
830
  }
×
831

×
832
  ZIPIOContextIterator Iter(ZIPIOEntryType type) const override {
833
    return {cache.Iter(type)};
834
  }
×
835

836
  ZIPIOContextCached(const std::string &file, es::MappedFile &&cacheFile)
837
      : ZIPIOContext_implbase(file), cacheMount(std::move(cacheFile)) {
×
838
    cache.Mount(cacheMount.data);
839
    auto &cacheHdr = reinterpret_cast<const CacheBaseHeader &>(cache.Header());
×
840
    auto zipData = static_cast<const char *>(zipMount.data);
841
    auto zipHeader = reinterpret_cast<const CacheBaseHeader *>(
×
842
        zipData + cacheHdr.zipCheckupOffset);
×
843

844
    if (memcmp(zipHeader, &cacheHdr, sizeof(cacheHdr))) {
845
      throw std::runtime_error("Cache header and zip checkup are different.");
846
    }
×
847
  }
×
848

849
private:
×
850
  Cache cache;
×
851
  es::MappedFile cacheMount;
852
};
×
853

×
854
std::unique_ptr<ZIPIOContext> MakeZIPContext(const std::string &file,
855
                                             const PathFilter &pathFilter,
×
856
                                             const PathFilter &moduleFilter) {
857
  return std::make_unique<ZIPIOContext_impl>(file, pathFilter, moduleFilter);
858
}
×
859

×
860
std::unique_ptr<ZIPIOContext> MakeZIPContext(const std::string &file) {
861
  std::string cacheFile = file + ".cache";
862
  es::MappedFile mf;
×
863
  try {
×
864
    mf = es::MappedFile(cacheFile);
×
865
  } catch (const std::exception &e) {
×
866
    printwarning("Failed loading cache: " << e.what());
×
867
    return std::make_unique<ZIPIOContext_impl>(file);
×
868
  }
×
869
  try {
870
    printinfo("Found zip cache: " << cacheFile);
×
871
    return std::make_unique<ZIPIOContextCached>(file, std::move(mf));
×
872
  } catch (const std::exception &e) {
873
    printwarning("Failed loading cache: " << e.what());
874
    return std::make_unique<ZIPIOContext_impl>(file);
875
  }
876
}
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