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

PredatorCZ / HavokLib / 82

24 Oct 2025 03:36PM UTC coverage: 67.014% (-0.9%) from 67.895%
82

push

github

PredatorCZ
add compendium support

2 of 65 new or added lines in 3 files covered. (3.08%)

62 existing lines in 3 files now uncovered.

1991 of 2971 relevant lines covered (67.01%)

143772.73 hits per line

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

0.0
/source/format_new.cpp
1
/*  Havok Format Library
2
    Copyright(C) 2016-2025 Lukas Cone
3

4
    This program is free software : you can redistribute it and / or modify
5
    it under the terms of the GNU General Public License as published by
6
    the Free Software Foundation, either version 3 of the License, or
7
    (at your option) any later version.
8

9
    This program is distributed in the hope that it will be useful,
10
    but WITHOUT ANY WARRANTY; without even the implied warranty of
11
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
12
    GNU General Public License for more details.
13

14
    You should have received a copy of the GNU General Public License
15
    along with this program.If not, see <https://www.gnu.org/licenses/>.
16
*/
17

18
#include "format_new.hpp"
19
#include "internal/hk_internal_api.hpp"
20
#include "spike/except.hpp"
21
#include "spike/io/binreader.hpp"
22
#include "spike/io/binwritter.hpp"
23
#include "spike/master_printer.hpp"
24
#include <map>
25

26
template <class C> void PtrGuard(const C *val) {
×
27
  if (!val) {
×
28
    throw std::runtime_error("nullptr detected");
×
29
  }
30
}
31

×
32
using ReadFunc = void (*)(BinReaderRef, hkChunk *, hkxNewHeader *);
×
33

×
34
template <uint32 tag>
35
ReadFunc Read = [](BinReaderRef, hkChunk *, hkxNewHeader *) {
NEW
36
  throw std::logic_error("Unimplemented function");
×
NEW
37
};
×
38

×
39
template <>
40
ReadFunc Read<1> = [](BinReaderRef rd, hkChunk *holder, hkxNewHeader *) {
UNCOV
41
  PtrGuard(holder);
×
UNCOV
42

×
UNCOV
43
  rd.Skip(holder->Size());
×
44
};
45

46
using ReadCompFunc = void (*)(BinReaderRef, hkChunk *, hkCompendiumData *);
47

48
template <uint32 tag>
49
ReadCompFunc ReadComp = [](BinReaderRef, hkChunk *, hkCompendiumData *) {
50
  throw std::logic_error("Unimplemented function");
51
};
52

53
template <>
54
ReadCompFunc ReadComp<1> =
55
    [](BinReaderRef rd, hkChunk *holder, hkCompendiumData *) {
56
      PtrGuard(holder);
57

58
      rd.Skip(holder->Size());
59
    };
60

61
template <>
62
ReadFunc Read<CompileFourCC("INDX")> =
63
    [](BinReaderRef, hkChunk *, hkxNewHeader *) {};
64

65
template <>
66
ReadCompFunc ReadComp<CompileFourCC("TYPE")> =
67
    [](BinReaderRef, hkChunk *, hkCompendiumData *) {};
68

69
template <>
70
ReadFunc Read<CompileFourCC("SDKV")> =
71
    [](BinReaderRef rd, hkChunk *, hkxNewHeader *root) {
72
      PtrGuard(root);
73
      char buff[8];
74

75
      rd.Read(buff);
76
      buff[4] = 0;
77

78
      uint32 version = atoi(buff);
79

80
      auto convert = [&] {
81
        switch (version) {
82
        case 2015:
83
          return HK2015;
84
        case 2016:
85
          return HK2016;
86
        case 2017:
×
UNCOV
87
          return HK2017;
×
88
        case 2018:
89
          return HK2018;
90
        case 2019:
UNCOV
91
          return HK2019;
×
92
        default:
UNCOV
93
          return HKUNKVER;
×
94
        }
95
      };
96

97
      root->toolset = convert();
98

99
      if (root->toolset == HKUNKVER) {
100
        throw es::InvalidVersionError(version);
101
      }
102
    };
103

104
template <>
105
ReadFunc Read<CompileFourCC("DATA")> =
106
    [](BinReaderRef rd, hkChunk *holder, hkxNewHeader *root) {
107
      PtrGuard(holder);
108
      PtrGuard(root);
109

110
      const uint32 dataSize = holder->Size();
111
      rd.ReadContainer(root->dataBuffer, dataSize);
UNCOV
112
    };
×
113

NEW
114
void ReadTypeNames(BinReaderRef rd, hkChunk *holder, std::string &outBuffer,
×
NEW
115
                   hkCompendiumData::StrVec &outVec) {
×
116
  PtrGuard(holder);
117

118
  const uint32 bufferSize = holder->Size();
119
  rd.ReadContainer(outBuffer, bufferSize);
120
  char *buffer = &outBuffer[0];
121
  std::string_view lastName = buffer;
122

123
  for (uint32 t = 0; t < bufferSize; t++) {
124
    if (!buffer[t]) {
125
      outVec.push_back(lastName);
126
      lastName = buffer + t + 1;
127

128
      if (lastName.empty()) {
129
        break;
×
130
      }
131
    }
×
132
  }
133
}
134

×
135
template <>
136
ReadCompFunc ReadComp<CompileFourCC("TSTR")> =
137
    [](BinReaderRef rd, hkChunk *holder, hkCompendiumData *root) {
NEW
138
      ReadTypeNames(rd, holder, root->classNamesBuffer, root->classNames);
×
UNCOV
139
    };
×
UNCOV
140

×
UNCOV
141
template <>
×
142
ReadCompFunc ReadComp<CompileFourCC("TST1")> = ReadComp<CompileFourCC("TSTR")>;
NEW
143

×
144
template <>
145
ReadCompFunc ReadComp<CompileFourCC("FSTR")> =
146
    [](BinReaderRef rd, hkChunk *holder, hkCompendiumData *root) {
147
      ReadTypeNames(rd, holder, root->memberNamesBuffer, root->memberNames);
148
    };
149

150
template <>
151
ReadCompFunc ReadComp<CompileFourCC("FST1")> = ReadComp<CompileFourCC("FSTR")>;
152

153
int32 ReadCompressedInt(BinReaderRef rd) {
154
  uint8 firstInt;
155
  int32 resultInt = 0;
156

157
  rd.Read(firstInt);
158

159
  const bool flag1 = (firstInt & 0x80) == 0x80;
160
  const bool flag2 = (firstInt & 0xC0) == 0xC0;
161
  const bool flag3 = (firstInt & 0xE0) == 0xE0;
162

163
  if (flag3) {
164
    rd.Read(resultInt);
165
    resultInt |= (firstInt & 0xf) << 4;
166
  } else if (flag2) {
167
    throw std::logic_error("Unhandled int compression : 0xC0!");
UNCOV
168
  } else if (flag1) {
×
169
    uint8 secondInt;
UNCOV
170
    rd.Read(secondInt);
×
171
    resultInt = secondInt | ((static_cast<int32>(firstInt) & 0xf) << 8);
172
  } else
173
    resultInt = firstInt;
174

×
UNCOV
175
  return resultInt;
×
176
}
177

UNCOV
178
template <>
×
179
ReadCompFunc ReadComp<CompileFourCC("TNAM")> = [](BinReaderRef rd,
NEW
180
                                                  hkChunk *holder,
×
NEW
181
                                                  hkCompendiumData *root) {
×
UNCOV
182
  PtrGuard(holder);
×
UNCOV
183
  PtrGuard(root);
×
184

185
  const size_t savepos = rd.Tell();
186
  const uint32 numClasses = ReadCompressedInt(rd) - 1;
×
187

188
  root->weldedClassNames.resize(numClasses);
×
189

UNCOV
190
  for (auto &c : root->weldedClassNames) {
×
191
    const uint32 classNameIndex = ReadCompressedInt(rd);
192
    c.className = root->classNames[classNameIndex];
193

194
    uint8 numTemplateArgs;
×
195
    rd.Read(numTemplateArgs);
196
    c.templateArguments.resize(numTemplateArgs);
197

×
198
    for (auto &t : c.templateArguments) {
×
199
      uint32 argNameIndex = ReadCompressedInt(rd);
200
      t.argName = root->classNames[argNameIndex];
UNCOV
201

×
202
      argNameIndex = ReadCompressedInt(rd);
UNCOV
203
      t.argType = &root->weldedClassNames[argNameIndex - 1];
×
204
    }
205
  }
×
206

×
UNCOV
207
  const int32 diff = static_cast<int32>(holder->Size()) -
×
208
                     static_cast<int32>(rd.Tell() - savepos);
209

210
  if (diff < 0) {
UNCOV
211
    printwarning("[Havok] TNAM chunk read too much data, possible incorrect "
×
212
                 "class map, proceeding to next chunk.");
213
  }
×
UNCOV
214

×
UNCOV
215
  rd.Skip(diff);
×
216
};
217

×
NEW
218
template <>
×
219
ReadCompFunc ReadComp<CompileFourCC("TNA1")> = ReadComp<CompileFourCC("TNAM")>;
220

221
template <>
NEW
222
ReadCompFunc ReadComp<CompileFourCC("ITEM")> =
×
223
    [](BinReaderRef rd, hkChunk *holder, hkCompendiumData *root) {
224
      PtrGuard(holder);
UNCOV
225
      PtrGuard(root);
×
UNCOV
226

×
227
      const uint32 numFixups = holder->Size() / sizeof(classEntryFixup);
228

229
      rd.ReadContainer(root->classEntries, numFixups);
230
    };
×
231

232
template <>
233
ReadFunc Read<CompileFourCC("PTCH")> =
234
    [](BinReaderRef rd, hkChunk *holder, hkxNewHeader *root) {
235
      PtrGuard(holder);
236
      PtrGuard(root);
237
      hkCompendiumData &cData = root->Compendium();
UNCOV
238

×
NEW
239
      if (!cData.weldedClassNames.size()) {
×
UNCOV
240
        throw std::runtime_error("File is missing type infos.");
×
241
      }
242

×
243
      const size_t endPos = rd.Tell() + holder->Size();
UNCOV
244

×
245
      while (rd.Tell() < endPos) {
246
        uint32 classNameIndex;
247
        rd.Read(classNameIndex);
248

249
        std::string_view toclassname =
×
NEW
250
            cData.weldedClassNames[classNameIndex - 1].className;
×
251

×
UNCOV
252
        bool isHKArray = toclassname == "hkArray";
×
253
        bool isHKRelArray = toclassname == "hkRelArray";
UNCOV
254

×
UNCOV
255
        uint32 numPointers;
×
256
        rd.Read(numPointers);
257

258
        for (uint32 t = 0; t < numPointers; t++) {
×
259
          uint32 cPointer;
UNCOV
260
          rd.Read(cPointer);
×
261

262
          if (isHKRelArray) {
263
            uint32 *retarget =
264
                reinterpret_cast<uint32 *>(&root->dataBuffer[0] + cPointer);
NEW
265
            const classEntryFixup &xfix = cData.classEntries[*retarget];
×
266

UNCOV
267
            uint16 *relRetarget = reinterpret_cast<uint16 *>(retarget);
×
268
            *relRetarget = xfix.tag - cPointer;
×
269
            *(relRetarget + 1) = xfix.count;
270
          } else {
271
            uint64 *retarget =
272
                reinterpret_cast<uint64 *>(&root->dataBuffer[0] + cPointer);
NEW
273
            const classEntryFixup &xfix = cData.classEntries[*retarget];
×
274

275
            *retarget =
276
                reinterpret_cast<uint64>(xfix.tag + root->dataBuffer.data());
UNCOV
277

×
278
            if (isHKArray) {
279
              *(retarget + 1) = xfix.count;
×
UNCOV
280
            }
×
281
          }
282
        }
UNCOV
283
      }
×
284

×
285
      for (auto &f : cData.classEntries) {
286
        const int32 clsID = f.Size() - 1;
UNCOV
287

×
UNCOV
288
        if (clsID < 0) {
×
289
          continue;
UNCOV
290
        }
×
291

×
292
        std::string_view clName = cData.weldedClassNames[clsID].className;
UNCOV
293
        const JenHash chash(clName);
×
294
        CRule rule(root->toolset, false, 8); // No way to detect so far
×
295
        IhkVirtualClass *clsn = hkVirtualClass::Create(chash, rule);
296
        auto cls = const_cast<hkVirtualClass *>(
297
            safe_deref_cast<const hkVirtualClass>(clsn));
298

299
        if (cls) {
300
          cls->SetDataPointer(&root->dataBuffer[0] + f.tag);
×
301
          cls->className = clName;
×
302
          cls->AddHash(clName);
UNCOV
303
          cls->header = root;
×
UNCOV
304
          root->virtualClasses.emplace_back(clsn);
×
305
          cls->Process();
306
        }
307
      }
×
308

NEW
309
      es::Dispose(cData.classEntries);
×
NEW
310
    };
×
311

312
template <>
313
ReadFunc Read<CompileFourCC("TCRF")> =
NEW
314
    [](BinReaderRef rd, hkChunk *holder, hkxNewHeader *root) {
×
NEW
315
      PtrGuard(holder);
×
NEW
316
      PtrGuard(root);
×
NEW
317

×
NEW
318
      const size_t savepos = rd.Tell();
×
NEW
319

×
NEW
320
      uint64 signature;
×
321
      rd.Read(signature);
322

323
      const int32 diff = static_cast<int32>(holder->Size()) -
NEW
324
                         static_cast<int32>(rd.Tell() - savepos);
×
325
      rd.Skip(diff);
326

327
      if (!std::holds_alternative<hkCompendium *>(root->compendium)) {
328
        throw std::runtime_error("no external compendium to be referenced");
NEW
329
      }
×
NEW
330

×
NEW
331
      hkCompendium *comp = std::get<hkCompendium *>(root->compendium);
×
332
      if (!comp->signatures.contains(signature)) {
333
        throw std::runtime_error("external compendium signature mismatch");
334
      }
335
    };
336

337
template <uint32 fourcc> constexpr auto make() {
UNCOV
338
  return std::make_pair(fourcc, Read<fourcc>);
×
339
}
340

×
341
template <uint32 fourcc> constexpr auto makeSkip() {
UNCOV
342
  return std::make_pair(fourcc, Read<1>);
×
UNCOV
343
}
×
344

345
static const std::map<uint32, ReadFunc> hkChunkRegistry = {
UNCOV
346
    makeSkip<CompileFourCC("TAG0")>(), //
×
UNCOV
347
    make<CompileFourCC("INDX")>(),     //
×
UNCOV
348
    make<CompileFourCC("SDKV")>(),     //
×
349
    make<CompileFourCC("DATA")>(),     //
350
    make<CompileFourCC("PTCH")>(),     //
351
    make<CompileFourCC("TCRF")>(),     //
352
};
353

354
template <uint32 fourcc> constexpr auto makeComp() {
355
  return std::make_pair(fourcc, ReadComp<fourcc>);
356
}
357

358
template <uint32 fourcc> constexpr auto makeCompSkip() {
359
  return std::make_pair(fourcc, ReadComp<1>);
360
}
361

362
static const std::map<uint32, ReadCompFunc> hkCompChunkRegistry = {
363
    makeCompSkip<CompileFourCC("TPTR")>(), //
364
    makeCompSkip<CompileFourCC("TPAD")>(), //
365
    makeCompSkip<CompileFourCC("THSH")>(), //
366
    makeCompSkip<CompileFourCC("TBOD")>(), // TODO
367
    makeCompSkip<CompileFourCC("TBDY")>(), // TODO
368
    makeComp<CompileFourCC("TYPE")>(),     //
369
    makeComp<CompileFourCC("TSTR")>(),     //
370
    makeComp<CompileFourCC("FSTR")>(),     //
371
    makeComp<CompileFourCC("TNAM")>(),     //
372
    makeComp<CompileFourCC("TST1")>(),     //
373
    makeComp<CompileFourCC("FST1")>(),     //
374
    makeComp<CompileFourCC("TNA1")>(),     //
375
    makeComp<CompileFourCC("ITEM")>(),     //
376
};
377

378
void hkChunk::Reorder() {
379
  FByteswapper(sizeAndFlags);
380
  sizeAndFlags = ((sizeAndFlags & 0xffffff) - 8) | (sizeAndFlags & 0xff000000);
381
}
382

383
void hkxNewHeader::Load(BinReaderRef rd) {
384
  hkChunk hdr;
385
  rd.Read(hdr);
386
  hdr.Reorder();
387

388
  if (hdr.tag != HK_HEADER_TAG) {
389
    throw es::InvalidHeaderError(hdr.tag);
390
  }
391

392
  const uint32 fileSize = hdr.Size();
UNCOV
393

×
394
  while (rd.Tell() < fileSize) {
×
NEW
395
    hkChunk chunk;
×
396
    rd.Read(chunk);
397
    chunk.Reorder();
NEW
398

×
399
    if (hkCompChunkRegistry.count(chunk.tag)) {
400
      hkCompChunkRegistry.at(chunk.tag)(rd, &chunk, &Compendium());
401
      continue;
402
    }
NEW
403

×
NEW
404
    if (!hkChunkRegistry.count(chunk.tag)) {
×
405
      auto dec = es::InvalidHeaderError::DecompileFourCC(chunk.tag, 4);
406
      printwarning("[Havok] Unhandled tag chunk: " << dec);
407
      rd.Skip(chunk.Size());
408
      return;
NEW
409
    }
×
410

411
    hkChunkRegistry.at(chunk.tag)(rd, &chunk, this);
412
  }
413
}
UNCOV
414

×
415
void hkxNewHeader::DumpClassNames(std::ostream &str) {
×
NEW
416
  auto &cData = Compendium();
×
417

418
  for (auto &cl : cData.weldedClassNames) {
419
    str << cl.className;
UNCOV
420

×
UNCOV
421
    if (cl.templateArguments.size()) {
×
UNCOV
422
      str << '<';
×
423

424
      for (auto &t : cl.templateArguments) {
425
        str << "typename " << t.argName << " = " << t.argType->className
UNCOV
426
            << ", ";
×
427
      }
428

429
      str.seekp(-2, std::ios_base::cur);
430
      str << '>';
×
431
    }
×
432

UNCOV
433
    str << '\n';
×
434
  }
435
}
NEW
436

×
NEW
437
void hkCompendium::Load(BinReaderRef rd) {
×
438
  hkChunk hdr;
NEW
439
  rd.Read(hdr);
×
NEW
440
  hdr.Reorder();
×
NEW
441

×
442
  if (hdr.tag != HK_HEADER_TCM) {
443
    throw es::InvalidHeaderError(hdr.tag);
NEW
444
  }
×
NEW
445

×
446
  {
447
    hkChunk chunk;
NEW
448
    rd.Read(chunk);
×
449
    chunk.Reorder();
450

451
    if (chunk.tag != CompileFourCC("TCID")) {
NEW
452
      throw es::InvalidHeaderError(chunk.tag);
×
453
    }
454

455
    const uint32 numSignatures = chunk.Size() / sizeof(uint64);
456
    std::vector<uint64> signatures;
NEW
457

×
NEW
458
    rd.ReadContainer(signatures, numSignatures);
×
459
    this->signatures = {signatures.begin(), signatures.end()};
460
  }
461

462
  const uint32 fileSize = hdr.Size();
463

464
  while (rd.Tell() < fileSize) {
465
    hkChunk chunk;
NEW
466
    rd.Read(chunk);
×
NEW
467
    chunk.Reorder();
×
468

469
    if (!hkCompChunkRegistry.count(chunk.tag)) {
NEW
470
      auto dec = es::InvalidHeaderError::DecompileFourCC(chunk.tag, 4);
×
NEW
471
      printwarning("[Havok] Unhandled tag chunk: " << dec);
×
472
      rd.Skip(chunk.Size());
NEW
473
      return;
×
NEW
474
    }
×
475

476
    hkCompChunkRegistry.at(chunk.tag)(rd, &chunk, this);
477
  }
478
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc