WoW Model Viewer
Your premiere tool for viewing, equipping and animating World of Warcraft models.
Loading...
Searching...
No Matches
DB2Reader.cpp
Go to the documentation of this file.
1#include "DB2Reader.h"
2#include "Logger.h"
3#include "Game.h"
4
5#include <algorithm>
6#include <cstring>
7#include <sstream>
8
9#define WDC_READ_DEBUG 1
10
11// ── magic constants ──────────────────────────────────────────────────────────
12static constexpr uint32_t MAGIC_WDC2 = 0x32434457; // 'WDC2'
13static constexpr uint32_t MAGIC_CLS1 = 0x434C5331; // 'CLS1' (alias for WDC2)
14static constexpr uint32_t MAGIC_WDC3 = 0x33434457; // 'WDC3'
15static constexpr uint32_t MAGIC_WDC4 = 0x34434457; // 'WDC4'
16static constexpr uint32_t MAGIC_WDC5 = 0x35434457; // 'WDC5'
17
18// ── helpers ──────────────────────────────────────────────────────────────────
19static int versionFromMagic(uint32_t magic)
20{
21 switch (magic)
22 {
23 case MAGIC_WDC2: return 2;
24 case MAGIC_CLS1: return 2;
25 case MAGIC_WDC3: return 3;
26 case MAGIC_WDC4: return 4;
27 case MAGIC_WDC5: return 5;
28 default: return 0;
29 }
30}
31
32// ─────────────────────────────────────────────────────────────────────────────
33
34DB2Reader::DB2Reader(const std::string& file)
35 : CASCFile(file)
36{
37}
38
40{
42 delete[] m_palletData;
43 // m_fileData is the CASCFile buffer — do NOT delete it here;
44 // CASCFile::close() handles the buffer.
45}
46
48{
49 m_fileData = nullptr;
51 return CASCFile::close();
52}
53
54// ─────────────────────────────────────────────────────────────────────────────
56{
57 // Must use memory-buffered mode (true) because DB2Reader stores direct
58 // pointers into the file data for random access during get() calls.
59 // open(false) streams via CascReadFile and getBuffer() returns nullptr.
60 if (!CASCFile::open(true))
61 {
62 LOG_ERROR << "DB2Reader: failed to open file " << fullname();
63 return false;
64 }
65
68
69 if (m_fileDataSize < sizeof(WDCHeader))
70 {
71 LOG_ERROR << "DB2Reader: file too small " << fullname();
72 return false;
73 }
74
75 // ── 1. Read magic and determine version ──────────────────────────────
76 uint32_t magic = 0;
77 read(&magic, 4);
78
80 if (m_wdcVersion == 0)
81 {
82 LOG_ERROR << "DB2Reader: unsupported magic in " << fullname();
83 return false;
84 }
85
86 // WDC5 inserts versionNum(4) + schemaString(128) between magic and the
87 // rest of the header.
88 if (m_wdcVersion == 5)
89 {
90 seek(4); // already past magic
91 uint32_t versionNum;
92 read(&versionNum, 4);
93 char schemaString[128];
94 read(schemaString, 128);
95 }
96
97 // Read the common header (record_count onward)
98 // We already consumed magic (and the WDC5 extra), so read the rest.
99 const size_t headerRemainder = sizeof(WDCHeader) - sizeof(m_header.magic);
100 read(&m_header.record_count, headerRemainder);
101 std::memcpy(m_header.magic, &magic, 4);
102
103#if WDC_READ_DEBUG > 0
104 LOG_INFO << "DB2Reader:" << fullname() << "version=" << m_wdcVersion
105 << "records=" << m_header.record_count
106 << "fields=" << m_header.field_count
107 << "recordSize=" << m_header.record_size
108 << "sections=" << m_header.section_count
109 << "flags=0x" << std::hex << m_header.flags << std::dec;
110#endif
111
112 LOG_INFO << "DB2Reader step 1 done: version=" << m_wdcVersion
113 << " fileSize=" << m_fileDataSize << " pos=" << getPos()
114 << " for " << fullname();
115
116 // ── 2. Section headers ───────────────────────────────────────────────
117 const uint32_t sectionCount = m_header.section_count;
118 m_sections.resize(sectionCount);
119
120 for (uint32_t i = 0; i < sectionCount; i++)
121 {
122 SectionData& sec = m_sections[i];
123 sec.isNormal = !(m_header.flags & 0x01);
124
125 if (m_wdcVersion == 2)
126 {
128 read(&sh, sizeof(sh));
129 sec.tactKeyHash = sh.tact_key_hash;
130 sec.fileOffset = sh.file_offset;
131 sec.recordCount = sh.record_count;
132 sec.stringTableSize = sh.string_table_size;
133 sec.offsetRecordsEnd = sh.offset_map_offset;
134 sec.idListSize = sh.id_list_size;
135 sec.relationshipDataSize = sh.relationship_data_size;
136 sec.offsetMapIdCount = 0;
137 sec.copyTableCount = sh.copy_table_size / 8;
138 }
139 else
140 {
142 read(&sh, sizeof(sh));
143 sec.tactKeyHash = sh.tact_key_hash;
144 sec.fileOffset = sh.file_offset;
145 sec.recordCount = sh.record_count;
146 sec.stringTableSize = sh.string_table_size;
147 sec.offsetRecordsEnd = sh.offset_records_end;
148 sec.idListSize = sh.id_list_size;
149 sec.relationshipDataSize = sh.relationship_data_size;
150 sec.offsetMapIdCount = sh.offset_map_id_count;
151 sec.copyTableCount = sh.copy_table_count;
152 }
153 }
154
155 LOG_INFO << "DB2Reader step 2 done: sectionCount=" << sectionCount
156 << " pos=" << getPos() << " for " << fullname();
157
158 // ── 3. Field structures ──────────────────────────────────────────────
159 {
160 std::vector<FieldStructure> fields(m_header.total_field_count);
161 read(fields.data(), m_header.total_field_count * sizeof(FieldStructure));
162 for (uint32_t i = 0; i < m_header.total_field_count; i++)
163 m_fieldSizes[fields[i].position] = fields[i].size;
164 }
165
166 LOG_INFO << "DB2Reader step 3 done: totalFieldCount=" << m_header.total_field_count
167 << " pos=" << getPos() << " for " << fullname();
168
169 // ── 4. Field storage info ────────────────────────────────────────────
171 {
172 const uint32_t count = m_header.field_storage_info_size / sizeof(FieldStorageInfo);
173 m_fieldStorageInfo.resize(count);
175 }
176
177 LOG_INFO << "DB2Reader step 4 done: fieldStorageInfoSize=" << m_header.field_storage_info_size
178 << " pos=" << getPos() << " for " << fullname();
179
180 // ── 5. Pallet data ──────────────────────────────────────────────────
182 {
183 m_palletData = new unsigned char[m_header.pallet_data_size];
185
186 uint32_t fieldId = 0;
187 uint32_t offset = 0;
188 for (auto& info : m_fieldStorageInfo)
189 {
190 if ((info.storage_type == COMP_BITPACKED_INDEXED ||
191 info.storage_type == COMP_BITPACKED_INDEXED_ARRAY) &&
192 info.additional_data_size != 0)
193 {
194 m_palletBlockOffsets[fieldId] = offset;
195 offset += info.additional_data_size;
196 }
197 fieldId++;
198 }
199 }
200
201 LOG_INFO << "DB2Reader step 5 done: palletDataSize=" << m_header.pallet_data_size
202 << " pos=" << getPos() << " for " << fullname();
203
204 // ── 6. Common data ──────────────────────────────────────────────────
206 {
207 unsigned char* commonRaw = new unsigned char[m_header.common_data_size];
208 read(commonRaw, m_header.common_data_size);
209
210 uint32_t fieldId = 0;
211 size_t offset = 0;
212 for (auto& info : m_fieldStorageInfo)
213 {
214 if (info.storage_type == COMP_COMMON_DATA && info.additional_data_size != 0)
215 {
216 unsigned char* ptr = commonRaw + offset;
217 std::map<uint32_t, uint32_t> vals;
218 for (uint32_t i = 0; i < info.additional_data_size / 8; i++)
219 {
220 uint32_t id, val;
221 std::memcpy(&id, ptr, 4); ptr += 4;
222 std::memcpy(&val, ptr, 4); ptr += 4;
223 vals[id] = val;
224 }
225 m_commonData[fieldId] = std::move(vals);
226 offset += info.additional_data_size;
227 }
228 fieldId++;
229 }
230 delete[] commonRaw;
231 }
232
233 LOG_INFO << "DB2Reader step 6 done: commonDataSize=" << m_header.common_data_size
234 << " pos=" << getPos() << " for " << fullname();
235
236 // ── 7. WDC4+ inter-section data ─────────────────────────────────────
237 // Per WebWowViewerCpp: only encrypted sections have inter-section data.
238 if (m_wdcVersion > 3)
239 {
240 for (uint32_t si = 0; si < sectionCount; si++)
241 {
242 if (m_sections[si].tactKeyHash == 0)
243 continue;
244
245 uint32_t entryCount = 0;
246 read(&entryCount, 4);
247 seekRelative(entryCount * 4);
248 }
249 }
250
251 LOG_INFO << "DB2Reader step 7 done: pos=" << getPos()
252 << " eof=" << isEof() << " for " << fullname();
253
254 // ── 8. Data sections ────────────────────────────────────────────────
255 // Approach follows WebWowViewerCpp: seek to sec.fileOffset (absolute)
256 // at the top of each section, read ALL data (even for encrypted sections)
257 // sequentially, then check encryption afterward in step 9.
258 uint32_t previousStringTableSize = 0;
259
260 for (uint32_t si = 0; si < sectionCount; si++)
261 {
262 SectionData& sec = m_sections[si];
263
264 // Seek to the section's declared absolute file offset.
265 // This eliminates cumulative offset drift across sections.
266 seek(sec.fileOffset);
267
268 LOG_INFO << "DB2Reader step 8: section " << si
269 << " recordCount=" << sec.recordCount
270 << " stringTableSize=" << sec.stringTableSize
271 << " idListSize=" << sec.idListSize
272 << " copyTableCount=" << sec.copyTableCount
273 << " relDataSize=" << sec.relationshipDataSize
274 << " offsetMapIdCount=" << sec.offsetMapIdCount
275 << " tactKey=" << sec.tactKeyHash
276 << " fileOffset=" << sec.fileOffset
277 << " pos=" << getPos()
278 << " for " << fullname();
279
280 const uint32_t recordDataOfs = sec.fileOffset;
281 const uint32_t recordDataSize = sec.isNormal
283 : (sec.offsetRecordsEnd - sec.fileOffset);
284
285 // Bounds-check: if the record data extends past the file buffer,
286 // mark the section as unusable.
287 if (recordDataOfs + recordDataSize > m_fileDataSize)
288 {
289 LOG_INFO << "DB2Reader step 8: section " << si
290 << " record data out of bounds (ofs=" << recordDataOfs
291 << " size=" << recordDataSize
292 << " fileSize=" << m_fileDataSize
293 << "), marking encrypted for " << fullname();
294 sec.recordDataPtr = nullptr;
295 sec.recordDataSize = 0;
296 sec.isEncrypted = true;
297 continue;
298 }
299
300 sec.recordDataPtr = m_fileData + recordDataOfs;
301 sec.recordDataSize = recordDataSize;
302
303 // Skip past record data
304 seekRelative(recordDataSize);
305
306 // String block
307 sec.stringTableOffset = static_cast<uint32_t>(getPos());
308 sec.previousStringTableSize = previousStringTableSize;
309 if (m_wdcVersion > 2)
310 previousStringTableSize += sec.stringTableSize;
312
313 // For encrypted sections, we still need to advance past sub-section
314 // data to maintain offset tracking. We read id list / copy table /
315 // offset map even for encrypted sections (they'll be zero-filled);
316 // step 9 will detect the encryption and ignore the records.
317
318 // id list
319 if (sec.idListSize > 0)
320 {
321 const uint32_t count = sec.idListSize / 4;
322 sec.idList.resize(count);
323 read(sec.idList.data(), sec.idListSize);
324 }
325
326 // copy table
327 if (sec.copyTableCount > 0)
328 {
329 if (sec.tactKeyHash != 0)
330 {
331 // Encrypted: skip, don't parse (values are garbage)
333 }
334 else
335 {
336 for (uint32_t i = 0; i < sec.copyTableCount; i++)
337 {
338 CopyTableEntry entry{};
339 read(&entry, sizeof(entry));
340 if (entry.newRowId != entry.copiedRowId)
341 m_copyTable[entry.newRowId] = entry.copiedRowId;
342 }
343 }
344 }
345
346 // ItemSparse (table_hash 145293629) has an extra offset_map_id_count*4
347 // block before the offset map — skip it. (Matches WebWowViewerCpp.)
348 if (m_header.table_hash == 145293629)
350
351 // offset map (WDC3+)
352 if (m_wdcVersion > 2 && sec.offsetMapIdCount > 0)
353 {
354 sec.offsetMap.resize(sec.offsetMapIdCount);
355 read(sec.offsetMap.data(), sec.offsetMapIdCount * sizeof(OffsetMapEntry));
356 }
357
358 // WDC2 offset map (different location)
359 if (m_wdcVersion == 2 && !sec.isNormal)
360 {
361 const uint32_t mapCount = m_header.max_id - m_header.min_id + 1;
362 seek(sec.offsetRecordsEnd); // offsetMapOffset for WDC2
363 sec.offsetMap.resize(mapCount);
364 read(sec.offsetMap.data(), mapCount * sizeof(OffsetMapEntry));
365 }
366
367 // relationship map
368 if (sec.relationshipDataSize > 0)
369 {
370 const size_t relStart = getPos();
371
372 if (sec.tactKeyHash != 0)
373 {
374 // Encrypted: skip relationship data entirely
376 }
377 else
378 {
379 uint32_t numEntries = 0;
380 read(&numEntries, 4);
381 seekRelative(8); // skip min/max relationship ID
382
383 for (uint32_t i = 0; i < numEntries; i++)
384 {
385 uint32_t foreignID, recIndex;
386 read(&foreignID, 4);
387 read(&recIndex, 4);
388 sec.relationshipMap[recIndex] = foreignID;
389
390 m_relationshipLookup[foreignID]; // ensure entry exists
391 }
392
393 // Advance to expected end in case of read mismatch
394 const size_t expected = relStart + sec.relationshipDataSize;
395 if (getPos() != expected)
396 seek(expected);
397 }
398 }
399
400 // offset map id list (WDC3+ — duplicate of id list for offset records)
401 if (m_wdcVersion > 2 && sec.offsetMapIdCount > 0)
403 }
404
405 LOG_INFO << "DB2Reader step 8 done: data sections parsed, pos=" << getPos()
406 << " eof=" << isEof() << " for " << fullname();
407
408 // ── 9. Detect encrypted sections & build record locations ───────────
409 uint32_t totalRecordCount = 0;
410
411 for (uint32_t si = 0; si < sectionCount; si++)
412 {
413 SectionData& sec = m_sections[si];
414
415 LOG_INFO << "DB2Reader step 9: section " << si
416 << " recordDataSize=" << sec.recordDataSize
417 << " tactKey=" << sec.tactKeyHash
418 << " for " << fullname();
419
420 // Encrypted section detection
421 if (sec.tactKeyHash != 0)
422 {
423 bool isZeroed = true;
424
425 // Bounds-check before scanning bytes — recordDataPtr may be
426 // invalid if offsets drifted during step 8.
427 if (!sec.recordDataPtr ||
429 {
430 LOG_INFO << "DB2Reader: section " << si
431 << " record data out of file bounds, treating as encrypted in "
432 << fullname();
433 sec.isEncrypted = true;
434 continue;
435 }
436
437 // check if record data is all zeroes
438 for (uint32_t i = 0; i < sec.recordDataSize && isZeroed; i++)
439 {
440 if (sec.recordDataPtr[i] != 0x00)
441 isZeroed = false;
442 }
443
444 // check if first integer after string block is non-zero
445 if (isZeroed && m_wdcVersion > 2 && sec.isNormal &&
446 (sec.idListSize > 0 || sec.copyTableCount > 0))
447 {
448 const unsigned char* ptr = m_fileData + sec.stringTableOffset + sec.stringTableSize;
449 if (ptr + 4 <= m_fileData + m_fileDataSize)
450 {
451 uint32_t firstVal = 0;
452 std::memcpy(&firstVal, ptr, 4);
453 isZeroed = (firstVal == 0);
454 }
455 }
456
457 // check first offset map entry
458 if (isZeroed && m_wdcVersion > 2 && sec.offsetMapIdCount > 0)
459 isZeroed = (sec.offsetMap[0].size == 0);
460
461 if (isZeroed)
462 {
463 LOG_INFO << "DB2Reader: skipping encrypted section " << si << " in " << fullname();
464 sec.isEncrypted = true;
465 continue;
466 }
467 }
468
469 // Build record locations for this section
470 const bool hasIDMap = !sec.idList.empty();
471 const bool emptyIDMap = hasIDMap &&
472 std::all_of(sec.idList.begin(), sec.idList.end(), [](uint32_t id) { return id == 0; });
473
474 // If no ID list, read IDs from inline field
475 if (!hasIDMap)
476 {
478 for (uint32_t i = 0; i < sec.recordCount; i++)
479 {
480 uint32_t id = 0;
481 const unsigned char* recPtr = sec.recordDataPtr + (i * m_header.record_size);
482
483 switch (idInfo.storage_type)
484 {
485 case COMP_NONE:
486 {
487 const uint32_t fieldSize = idInfo.field_size_bits / 8;
488 std::memcpy(&id, recPtr + idInfo.field_offset_bits / 8, std::min(fieldSize, 4u));
489 if (idInfo.field_size_bits < 32)
490 id &= (1u << idInfo.field_size_bits) - 1;
491 break;
492 }
493 case COMP_BITPACKED:
495 {
496 id = static_cast<uint32_t>(readBitpackedValue64(idInfo, recPtr));
497 break;
498 }
499 default:
500 LOG_ERROR << "DB2Reader: unsupported ID compression type " << idInfo.storage_type;
501 return false;
502 }
503
504 RecordLocation loc;
505 loc.sectionIndex = si;
506 loc.localIndex = i;
507 loc.recordID = id;
508 m_recordLocations.push_back(loc);
509 }
510 }
511 else
512 {
513 for (uint32_t i = 0; i < sec.recordCount; i++)
514 {
515 RecordLocation loc;
516 loc.sectionIndex = si;
517 loc.localIndex = i;
518 loc.recordID = (hasIDMap && !emptyIDMap) ? sec.idList[i] : i;
519 m_recordLocations.push_back(loc);
520 }
521 }
522
523 totalRecordCount += sec.recordCount;
524 }
525
526 LOG_INFO << "DB2Reader step 9 done: recordLocations=" << m_recordLocations.size()
527 << " for " << fullname();
528
529 // ── 10. Inflate copy table ──────────────────────────────────────────
530 // Build a recordID -> RecordLocation map for source lookup
531 std::unordered_map<uint32_t, size_t> idToLocationIndex;
532 for (size_t i = 0; i < m_recordLocations.size(); i++)
533 idToLocationIndex[m_recordLocations[i].recordID] = i;
534
535 for (const auto& [destID, srcID] : m_copyTable)
536 {
537 auto it = idToLocationIndex.find(srcID);
538 if (it != idToLocationIndex.end())
539 {
540 RecordLocation loc = m_recordLocations[it->second];
541 loc.recordID = destID;
542 m_recordLocations.push_back(loc);
543 }
544 }
545
546 LOG_INFO << "DB2Reader step 10 done: totalRecords=" << m_recordLocations.size()
547 << " copyTable=" << m_copyTable.size() << " for " << fullname();
548
549 // Build ID-to-record-index lookup for O(1) getRow() by ID
550 m_idToRecordIndex.clear();
551 m_idToRecordIndex.reserve(m_recordLocations.size());
552 for (size_t i = 0; i < m_recordLocations.size(); i++)
553 m_idToRecordIndex[m_recordLocations[i].recordID] = i;
554
555 LOG_INFO << "DB2Reader step 11 done: idToRecordIndex built for " << fullname();
556
557 // Populate relationship reverse lookup using a map for O(1) lookups
558 {
559 std::unordered_map<uint64_t, uint32_t> sectionLocalToRecordID;
560 for (const auto& loc : m_recordLocations)
561 sectionLocalToRecordID[(uint64_t(loc.sectionIndex) << 32) | loc.localIndex] = loc.recordID;
562
563 for (uint32_t si = 0; si < sectionCount; si++)
564 {
565 const SectionData& sec = m_sections[si];
566 for (const auto& [recIndex, foreignID] : sec.relationshipMap)
567 {
568 const uint64_t key = (uint64_t(si) << 32) | recIndex;
569 auto it = sectionLocalToRecordID.find(key);
570 if (it != sectionLocalToRecordID.end())
571 m_relationshipLookup[foreignID].push_back(it->second);
572 }
573 }
574 }
575
576 LOG_INFO << "DB2Reader: parsed " << fullname()
577 << " version=" << m_wdcVersion
578 << " records=" << m_recordLocations.size()
579 << " sections=" << sectionCount;
580
581 return true;
582}
583
584// ─────────────────────────────────────────────────────────────────────────────
585const unsigned char* DB2Reader::getRecordOffset(unsigned int sectionIndex,
586 unsigned int recordIndexInSection) const
587{
588 const SectionData& sec = m_sections[sectionIndex];
589 if (sec.isNormal)
590 return sec.recordDataPtr + (recordIndexInSection * m_header.record_size);
591
592 // sparse / offset-map records
593 if (m_wdcVersion == 2)
594 {
595 // WDC2: offset map is indexed by (recordID - min_id)
596 // we need to find the recordID for this index
597 const uint32_t recordID = sec.idList.empty()
598 ? recordIndexInSection
599 : sec.idList[recordIndexInSection];
600 const uint32_t mapIndex = recordID - m_header.min_id;
601 if (mapIndex < sec.offsetMap.size())
602 return m_fileData + sec.offsetMap[mapIndex].offset;
603 }
604 else
605 {
606 // WDC3+: offset map indexed by recordIndexInSection
607 if (recordIndexInSection < sec.offsetMap.size())
608 return m_fileData + sec.offsetMap[recordIndexInSection].offset;
609 }
610
611 return sec.recordDataPtr;
612}
613
614// ─────────────────────────────────────────────────────────────────────────────
616 const unsigned char* recordOffset) const
617{
618 const uint32_t byteOffset = info.field_offset_bits / 8;
619 const uint32_t numBytes = (info.field_size_bits + (info.field_offset_bits & 7) + 7) / 8;
620
621 uint64_t raw = 0;
622 std::memcpy(&raw, recordOffset + byteOffset, std::min(numBytes, 8u));
623
624 raw >>= (info.field_offset_bits & 7);
625 raw &= (1ull << info.field_size_bits) - 1;
626 return raw;
627}
628
629// ─────────────────────────────────────────────────────────────────────────────
630bool DB2Reader::readFieldValue(unsigned int sectionIndex,
631 unsigned int recordIndexInSection,
632 unsigned int fieldIndex,
633 unsigned int arrayIndex,
634 unsigned int arraySize,
635 uint32_t recordID,
636 unsigned int& result) const
637{
638 const unsigned char* recordOffset = getRecordOffset(sectionIndex, recordIndexInSection);
639 const FieldStorageInfo& info = m_fieldStorageInfo[fieldIndex];
640
641 switch (info.storage_type)
642 {
643 case COMP_NONE:
644 {
645 uint32_t fieldSize = info.field_size_bits / 8;
646 const unsigned char* fieldOffset = recordOffset + info.field_offset_bits / 8;
647
648 if (arraySize != 1)
649 {
650 fieldSize /= arraySize;
651 fieldOffset += ((info.field_size_bits / 8 / arraySize) * arrayIndex);
652 }
653
654 result = 0;
655 std::memcpy(&result, fieldOffset, std::min(fieldSize, 4u));
656 {
657 const uint32_t bitsPerElement = info.field_size_bits / arraySize;
658 if (bitsPerElement < 32)
659 result &= (1u << bitsPerElement) - 1;
660 }
661 break;
662 }
663 case COMP_BITPACKED:
665 {
666 result = static_cast<uint32_t>(readBitpackedValue64(info, recordOffset));
667 break;
668 }
669 case COMP_COMMON_DATA:
670 {
671 result = info.val1; // default value
672 auto mapIt = m_commonData.find(fieldIndex);
673 if (mapIt != m_commonData.end())
674 {
675 auto valIt = mapIt->second.find(recordID);
676 if (valIt != mapIt->second.end())
677 result = valIt->second;
678 }
679 break;
680 }
682 {
683 const uint32_t index = static_cast<uint32_t>(readBitpackedValue64(info, recordOffset));
684 auto it = m_palletBlockOffsets.find(fieldIndex);
685 if (it == m_palletBlockOffsets.end())
686 {
687 LOG_ERROR << "DB2Reader: missing pallet offset for field " << fieldIndex;
688 return false;
689 }
690 const uint32_t offset = it->second + index * 4;
691 std::memcpy(&result, m_palletData + offset, 4);
692 break;
693 }
695 {
696 const uint32_t index = static_cast<uint32_t>(readBitpackedValue64(info, recordOffset));
697 auto it = m_palletBlockOffsets.find(fieldIndex);
698 if (it == m_palletBlockOffsets.end())
699 {
700 LOG_ERROR << "DB2Reader: missing pallet offset for field " << fieldIndex;
701 return false;
702 }
703 const uint32_t offset = it->second + index * arraySize * 4 + arrayIndex * 4;
704 std::memcpy(&result, m_palletData + offset, 4);
705 break;
706 }
707 default:
708 LOG_ERROR << "DB2Reader: unsupported compression type " << info.storage_type;
709 return false;
710 }
711
712 return true;
713}
714
715// get() was removed — DB2Table accesses record data directly via friend.
static constexpr uint32_t MAGIC_WDC5
Definition DB2Reader.cpp:16
static constexpr uint32_t MAGIC_CLS1
Definition DB2Reader.cpp:13
static constexpr uint32_t MAGIC_WDC2
Definition DB2Reader.cpp:12
static int versionFromMagic(uint32_t magic)
Definition DB2Reader.cpp:19
static constexpr uint32_t MAGIC_WDC3
Definition DB2Reader.cpp:14
static constexpr uint32_t MAGIC_WDC4
Definition DB2Reader.cpp:15
#define LOG_ERROR
Definition Logger.h:11
#define LOG_INFO
Definition Logger.h:10
GameFile implementation that reads from a CASC storage archive.
Definition CASCFile.h:20
size_t read(void *dest, size_t bytes)
Read bytes from the file into dest.
Definition CASCFile.cpp:22
void seek(size_t offset)
Seek to an absolute byte offset.
Definition CASCFile.cpp:38
WDCHeader m_header
Definition DB2Reader.h:158
std::map< uint32_t, std::map< uint32_t, uint32_t > > m_commonData
Definition DB2Reader.h:163
unsigned char * m_fileData
Definition DB2Reader.h:188
bool readFieldValue(unsigned int sectionIndex, unsigned int recordIndexInSection, unsigned int fieldIndex, unsigned int arrayIndex, unsigned int arraySize, uint32_t recordID, unsigned int &result) const
std::vector< SectionData > m_sections
Definition DB2Reader.h:165
uint64_t readBitpackedValue64(const FieldStorageInfo &info, const unsigned char *recordOffset) const
std::unordered_map< uint32_t, size_t > m_idToRecordIndex
Definition DB2Reader.h:178
unsigned char * m_palletData
Definition DB2Reader.h:162
const unsigned char * getRecordOffset(unsigned int sectionIndex, unsigned int recordIndexInSection) const
~DB2Reader() override
Definition DB2Reader.cpp:39
std::unordered_map< uint32_t, std::vector< uint32_t > > m_relationshipLookup
Definition DB2Reader.h:181
bool open()
Definition DB2Reader.cpp:55
bool close()
Definition DB2Reader.cpp:47
std::map< uint32_t, uint32_t > m_copyTable
Definition DB2Reader.h:184
std::map< uint32_t, uint32_t > m_palletBlockOffsets
Definition DB2Reader.h:161
size_t m_fileDataSize
Definition DB2Reader.h:189
std::vector< FieldStorageInfo > m_fieldStorageInfo
Definition DB2Reader.h:160
std::map< int, int > m_fieldSizes
Definition DB2Reader.h:191
int m_wdcVersion
Definition DB2Reader.h:156
DB2Reader(const std::string &file)
Definition DB2Reader.cpp:34
std::vector< RecordLocation > m_recordLocations
Definition DB2Reader.h:175
@ COMP_BITPACKED_SIGNED
Definition DB2Reader.h:35
@ COMP_BITPACKED_INDEXED_ARRAY
Definition DB2Reader.h:34
@ COMP_BITPACKED
Definition DB2Reader.h:31
@ COMP_BITPACKED_INDEXED
Definition DB2Reader.h:33
@ COMP_COMMON_DATA
Definition DB2Reader.h:32
bool open(bool useMemoryBuffer=true)
Open the file, optionally loading into a memory buffer.
Definition GameFile.cpp:41
size_t getPos()
Current read position.
Definition GameFile.cpp:139
size_t getSize()
Total size of the file in bytes.
Definition GameFile.cpp:134
unsigned char * getBuffer() const
Pointer to the start of the internal buffer.
Definition GameFile.cpp:144
unsigned long long size
Definition GameFile.h:83
bool close()
Close the file and release the internal buffer.
Definition GameFile.cpp:74
void seekRelative(size_t offset)
Advance the read pointer by offset bytes.
Definition GameFile.cpp:35
const std::string & fullname() const
Definition GameFile.h:56
bool isEof()
True if the read pointer has reached the end of the file.
Definition GameFile.cpp:24
std::vector< OffsetMapEntry > offsetMap
Definition DB2Reader.h:142
unsigned char * recordDataPtr
Definition DB2Reader.h:136
uint32_t relationshipDataSize
Definition DB2Reader.h:128
std::map< uint32_t, uint32_t > relationshipMap
Definition DB2Reader.h:143
std::vector< uint32_t > idList
Definition DB2Reader.h:141
uint32_t previousStringTableSize
Definition DB2Reader.h:139
uint32_t pallet_data_size
Definition DB2Reader.h:59
uint32_t field_storage_info_size
Definition DB2Reader.h:57
uint32_t common_data_size
Definition DB2Reader.h:58
uint32_t section_count
Definition DB2Reader.h:60
uint32_t total_field_count
Definition DB2Reader.h:54
uint32_t record_count
Definition DB2Reader.h:43