SourcePP
Several modern C++20 libraries for sanely parsing Valve's formats.
Loading...
Searching...
No Matches
MDL.cpp
Go to the documentation of this file.
1#include <mdlpp/structs/MDL.h>
2
3#include <BufferStream.h>
5
6using namespace mdlpp::MDL;
7using namespace sourcepp;
9
10constexpr int32_t MDL_ID = parser::binary::makeFourCC("IDST");
11
12namespace {
13
14// boundary check before seeking
15bool seekAndValidate(BufferStreamReadOnly& stream, const uint64_t offset) {
16 if (offset >= stream.size()) {
17 return false;
18 }
19 stream.seek_u(offset);
20 return true;
21}
22
23// TODO: Decompress RLE animation data into frame values
24// TODO: Make lambda return bool for the boundary check?
25void readAnimValueRLE(BufferStreamReadOnly& stream, const int frameCount, std::vector<AnimValue>& outData) {
26 AnimValue val{};
27 int framesCovered = 0;
28 while (framesCovered < frameCount) {
29 stream.read(val.value);
30 outData.push_back(val);
31
32 if (val.num.valid == 0) {
33 framesCovered += val.num.total;
34 stream.read(val.value);
35 outData.push_back(val);
36 } else {
37 // val.num.valid values, covering val.num.total frames
38 const int numValues = val.num.valid;
39 const int totalFrames = val.num.total;
40 for (int v = 0; v < numValues; v++) {
41 stream.read(val.value);
42 outData.push_back(val);
43 }
44 framesCovered += totalFrames;
45 }
46 }
47}
48
49} // namespace
50
51bool MDL::open(const std::byte* data, std::size_t size) {
52 BufferStreamReadOnly stream{data, size};
53
54 if (stream.read<int32_t>() != MDL_ID) {
55 return false;
56 }
57
58 if (stream.read(this->version); this->version < 44 || this->version > 49) {
59 return false;
60 }
61
62 stream
63 .read(this->checksum)
64 .read(this->name, 64)
65 .skip<int32_t>() // dataLength
66 .read(this->eyePosition)
67 .read(this->illuminationPosition)
68 .read(this->hullMin)
69 .read(this->hullMax)
70 .read(this->viewBBoxMin)
71 .read(this->viewBBoxMax);
72
73 this->flags = static_cast<Flags>(stream.read<int32_t>());
74
75 const auto boneCount = stream.read<int32_t>();
76 const auto boneOffset = stream.read<int32_t>();
77
78 const auto boneControllerCount = stream.read<int32_t>();
79 const auto boneControllerOffset = stream.read<int32_t>();
80
81 const auto hitboxSetCount = stream.read<int32_t>();
82 const auto hitboxSetOffset = stream.read<int32_t>();
83
84 const auto animDescCount = stream.read<int32_t>();
85 const auto animDescOffset = stream.read<int32_t>();
86
87 const auto sequenceDescCount = stream.read<int32_t>();
88 const auto sequenceDescOffset = stream.read<int32_t>();
89
90 stream
91 .read(this->activityListVersion)
92 .read(this->eventsIndexed);
93
94 const auto materialCount = stream.read<int32_t>();
95 const auto materialOffset = stream.read<int32_t>();
96
97 const auto materialDirCount = stream.read<int32_t>();
98 const auto materialDirOffset = stream.read<int32_t>();
99
100 const auto skinReferenceCount = stream.read<int32_t>();
101 const auto skinReferenceFamilyCount = stream.read<int32_t>();
102 const auto skinReferenceOffset = stream.read<int32_t>();
103
104 const auto bodyPartCount = stream.read<int32_t>();
105 const auto bodyPartOffset = stream.read<int32_t>();
106
107 const auto attachmentCount = stream.read<int32_t>();
108 const auto attachmentOffset = stream.read<int32_t>();
109
110 const auto localNodeCount = stream.read<int32_t>();
111 const auto localNodeIndex = stream.read<int32_t>();
112 const auto localNodeNameIndex = stream.read<int32_t>();
113
114 const auto flexDescCount = stream.read<int32_t>();
115 const auto flexDescIndex = stream.read<int32_t>();
116 const auto flexControllerCount = stream.read<int32_t>();
117 const auto flexControllerIndex = stream.read<int32_t>();
118 const auto flexRulesCount = stream.read<int32_t>();
119 const auto flexRulesIndex = stream.read<int32_t>();
120 const auto ikChainCount = stream.read<int32_t>();
121 const auto ikChainIndex = stream.read<int32_t>();
122
123 const auto mouthsCount = stream.read<int32_t>();
124 const auto mouthsIndex = stream.read<int32_t>();
125
126 const auto localPoseParamCount = stream.read<int32_t>();
127 const auto localPoseParamIndex = stream.read<int32_t>();
128
129 const auto surfacePropertyIndex = stream.read<int32_t>();
130 const auto keyValueIndex = stream.read<int32_t>();
131 const auto keyValueCount = stream.read<int32_t>();
132
133 const auto localIKAutoplayLockCount = stream.read<int32_t>();
134 const auto localIKAutoplayLockIndex = stream.read<int32_t>();
135
136 stream
137 .read(this->mass)
138 .read(this->contentsFlags);
139
140 const auto includeModelCount = stream.read<int32_t>();
141 const auto includeModelIndex = stream.read<int32_t>();
142 stream.skip<int32_t>(); // virtualModel
143
144 // do we want to parse these here?
145 const auto animationBlocksNameIndex = stream.read<int32_t>();
146 const auto animationBlocksCount = stream.read<int32_t>();
147 const auto animationBlocksIndex = stream.read<int32_t>();
148 stream.skip<int32_t>(); // animationBlockModel
149
150 const auto boneTableNameIndex = stream.read<int32_t>();
151
152 stream
153 .skip<int32_t>() // vertexBase
154 .skip<int32_t>() // offsetBase
155 .read(this->directionalDotProduct)
156 .read(this->rootLOD)
157 .read(this->numAllowedRootLODs)
158 .skip<uint8_t>() // _unused0
159 .skip<int32_t>(); // _unused1
160
161 const auto flexControllerUICount = stream.read<int32_t>();
162 const auto flexControllerUIIndex = stream.read<int32_t>();
163
164 stream
165 .read(this->vertAnimFixedPointScale)
166 .skip<int32_t>() // _unused2
167 .read(this->studioHdr2Index)
168 .skip<int32_t>(); // _unused3
169
170 // header 2
171 // Note: some models have weird (dynamic??) header2 data with offsets (and size) pointing outside the file.
172 // Unlike the rest of this parser, I validate offset AND size, then skip invalid sections
173 // rather than failing the whole file. (Might want to change this). - cueki Nov 27, 2025
174 if (this->studioHdr2Index != 0) {
175 stream.seek_u(this->studioHdr2Index)
176 .read(this->header2.srcBoneTransformCount)
177 .read(this->header2.srcBoneTransformIndex)
178 .read(this->header2.illumPositionAttachmentIndex)
179 .read(this->header2.maxEyeDeflection)
180 .read(this->header2.linearBoneIndex)
181 .read(this->header2.nameIndex)
182 .read(this->header2.boneFlexDriverCount)
183 .read(this->header2.boneFlexDriverIndex);
184 stream.skip<int32_t>(56); // reserved
185 this->hasHeader2 = true;
186
187 for (int i = 0; i < this->header2.srcBoneTransformCount; i++) {
188 const auto srcBoneTransformPos = this->studioHdr2Index + this->header2.srcBoneTransformIndex + i * (sizeof(int32_t) + sizeof(math::Mat3x4f) * 2);
189 if (srcBoneTransformPos + (sizeof(int32_t) + sizeof(math::Mat3x4f) * 2) > stream.size()) {
190 break;
191 }
192 auto& srcBoneTransform = this->srcBoneTransforms.emplace_back();
193
194 stream.seek_u(srcBoneTransformPos);
195 const auto nameOffset = stream.read<int32_t>();
196 const auto nameAbsOffset = srcBoneTransformPos + nameOffset;
197 if (nameOffset != 0 && nameAbsOffset > 0 && nameAbsOffset < stream.size()) {
198 stream.seek_u(nameAbsOffset)
199 .read(srcBoneTransform.name)
200 .seek_u(srcBoneTransformPos + sizeof(int32_t));
201 }
202
203 stream
204 .read(srcBoneTransform.pretransform)
205 .read(srcBoneTransform.posttransform);
206 }
207
208 if (this->header2.linearBoneIndex != 0) {
209 const auto linearBoneOffset = this->studioHdr2Index + this->header2.linearBoneIndex;
210 if (linearBoneOffset + sizeof(int32_t) * 16 <= stream.size()) {
211 this->linearBone = LinearBone{};
212 auto& lb = *this->linearBone;
213
214 stream.seek_u(linearBoneOffset)
215 .read(lb.boneCount);
216
217 const auto flagsIndex = stream.read<int32_t>();
218 const auto parentIndex = stream.read<int32_t>();
219 const auto posIndex = stream.read<int32_t>();
220 const auto quatIndex = stream.read<int32_t>();
221 const auto rotIndex = stream.read<int32_t>();
222 const auto poseToBoneIndex = stream.read<int32_t>();
223 const auto posScaleIndex = stream.read<int32_t>();
224 const auto rotScaleIndex = stream.read<int32_t>();
225 const auto qAlignmentIndex = stream.read<int32_t>();
226 stream.skip<int32_t>(6); // unused[6]
227
228 // we are reading from a struct of arrays instead of an array of structs
229 stream.seek_u(linearBoneOffset + flagsIndex);
230 for (int i = 0; i < lb.boneCount; i++) {
231 lb.flags.push_back(stream.read<int32_t>());
232 }
233
234 stream.seek_u(linearBoneOffset + parentIndex);
235 for (int i = 0; i < lb.boneCount; i++) {
236 lb.parent.push_back(stream.read<int32_t>());
237 }
238
239 stream.seek_u(linearBoneOffset + posIndex);
240 for (int i = 0; i < lb.boneCount; i++) {
241 lb.position.push_back(stream.read<math::Vec3f>());
242 }
243
244 stream.seek_u(linearBoneOffset + quatIndex);
245 for (int i = 0; i < lb.boneCount; i++) {
246 lb.quaternion.push_back(stream.read<math::Quat>());
247 }
248
249 stream.seek_u(linearBoneOffset + rotIndex);
250 for (int i = 0; i < lb.boneCount; i++) {
251 lb.rotation.push_back(stream.read<math::Vec3f>());
252 }
253
254 stream.seek_u(linearBoneOffset + poseToBoneIndex);
255 for (int i = 0; i < lb.boneCount; i++) {
256 lb.poseToBone.push_back(stream.read<math::Mat3x4f>());
257 }
258
259 stream.seek_u(linearBoneOffset + posScaleIndex);
260 for (int i = 0; i < lb.boneCount; i++) {
261 lb.positionScale.push_back(stream.read<math::Vec3f>());
262 }
263
264 stream.seek_u(linearBoneOffset + rotScaleIndex);
265 for (int i = 0; i < lb.boneCount; i++) {
266 lb.rotationScale.push_back(stream.read<math::Vec3f>());
267 }
268
269 stream.seek_u(linearBoneOffset + qAlignmentIndex);
270 for (int i = 0; i < lb.boneCount; i++) {
271 lb.quaternionAlignment.push_back(stream.read<math::Quat>());
272 }
273 }
274 }
275
276 for (int i = 0; i < this->header2.boneFlexDriverCount; i++) {
277 const auto boneFlexDriverPos = this->studioHdr2Index + this->header2.boneFlexDriverIndex + i * (sizeof(int32_t) * 6);
278 if (boneFlexDriverPos + (sizeof(int32_t) * 6) > stream.size()) {
279 break;
280 }
281 auto& boneFlexDriver = this->boneFlexDrivers.emplace_back();
282 stream.seek_u(boneFlexDriverPos)
283 .read(boneFlexDriver.boneIndex);
284
285 const auto controlCount = stream.read<int32_t>();
286 const auto controlIndex = stream.read<int32_t>();
287 stream.skip<int32_t>(3); // unused[3]
288
289 for (int j = 0; j < controlCount; j++) {
290 const auto controlPos = boneFlexDriverPos + controlIndex + j * (sizeof(int32_t) * 2 + sizeof(float) * 2);
291 if (controlPos + (sizeof(int32_t) * 2 + sizeof(float) * 2) > stream.size()) {
292 break;
293 }
294 auto& control = boneFlexDriver.controls.emplace_back();
295 stream.seek_u(controlPos)
296 .read(control.boneComponent)
297 .read(control.flexControllerIndex)
298 .read(control.min)
299 .read(control.max);
300 }
301 }
302 }
303
304 // Done reading sequentially, start seeking to offsets
305
306 stream.seek(boneOffset);
307 for (int i = 0; i < boneCount; i++) {
308 auto& bone = this->bones.emplace_back();
309
310 parser::binary::readStringAtOffset(stream, bone.name);
311 stream
312 .read(bone.parent)
313 .read(bone.boneController)
314 .read(bone.position)
315 .read(bone.rotationQuat)
316 .read(bone.rotationEuler)
317 .read(bone.positionScale)
318 .read(bone.rotationScale)
319 .read(bone.poseToBose)
320 .read(bone.alignment)
321 .read(bone.flags)
322 .read(bone.procType)
323 .read(bone.procIndex)
324 .read(bone.physicsBone);
325
326 // TODO: Parse procedural bone data at procIndex
327 // - STUDIO_PROC_AXISINTERP (1): mstudioaxisinterpbone_t - 152 bytes
328 // - STUDIO_PROC_QUATINTERP (2): mstudioquatinterpbone_t - 12 bytes + trigger array
329 // - STUDIO_PROC_AIMATBONE (3): mstudioaimatbone_t - 44 bytes
330 // - STUDIO_PROC_AIMATATTACH (4): mstudioaimatbone_t - 44 bytes
331 // - STUDIO_PROC_JIGGLE (5): mstudiojigglebone_t - 140 bytes (lol)
332
333 parser::binary::readStringAtOffset(stream, bone.surfacePropName, std::ios::cur, sizeof(int32_t) * 12 + sizeof(math::Vec3f) * 4 + sizeof(math::Quat) * 2 + sizeof(math::Mat3x4f) + sizeof(Bone::Flags));
334 stream.read(bone.contents);
335
336 // _unused0
337 stream.skip<int32_t>(8);
338 }
339
340 stream.seek(boneControllerOffset);
341 for (int i = 0; i < boneControllerCount; i++) {
342 this->boneControllers.push_back(stream.read<BoneController>());
343
344 // _unused0
345 stream.skip<int32_t>(8);
346 }
347 for (int i = 0; i < hitboxSetCount; i++) {
348 const auto hitboxSetPos = hitboxSetOffset + i * (sizeof(int32_t) * 3);
349 stream.seek_u(hitboxSetPos);
350
351 auto& hitboxSet = this->hitboxSets.emplace_back();
352
353 parser::binary::readStringAtOffset(stream, hitboxSet.name);
354 const auto hitboxCount = stream.read<int32_t>();
355 const auto hitboxOffset = stream.read<int32_t>();
356
357 for (int j = 0; j < hitboxCount; j++) {
358 const auto hitboxPos = hitboxOffset + j * (sizeof(int32_t) * 11 + sizeof(math::Vec3f) * 2);
359 if (!seekAndValidate(stream, hitboxSetPos + hitboxPos)) {
360 return false;
361 }
362
363 auto& hitbox = hitboxSet.hitboxes.emplace_back();
364
365 stream
366 .read(hitbox.bone)
367 .read(hitbox.group)
368 .read(hitbox.bboxMin)
369 .read(hitbox.bboxMax);
370
371 // note: we don't know what model versions use absolute vs. relative offsets here
372 // and this is unimportant, so skip parsing the bbox name here
373 //readStringAtOffset(stream, hitbox.name, std::ios::cur, sizeof(int32_t) * 3 + sizeof(Vec3f) * 2);
374 stream.skip<int32_t>();
375 hitbox.name = "";
376
377 // _unused0
378 stream.skip<int32_t>(8);
379 }
380 }
381
382 stream.seek(animDescOffset);
383 for (int i = 0; i < animDescCount; i++) {
384 const auto animDescPos = animDescOffset + i * (sizeof(int32_t) * 22 + sizeof(int16_t) * 2 + sizeof(float) * 2);
385 stream.seek_u(animDescPos);
386
387 auto& animDesc = this->animations.emplace_back();
388
389 stream.skip<int32_t>(); // basePointer
390 // animation name offsets are relative to the struct base
391 // after the offset int32, we are 8 bytes past the structure base (4 for basePointer + 4 for offset)
392 // so we subtract 8 instead of the default 4
393 parser::binary::readStringAtOffset(stream, animDesc.name, std::ios::cur, 8);
394 stream
395 .read(animDesc.fps)
396 .read(animDesc.flags)
397 .read(animDesc.frameCount);
398
399 const auto movementCount = stream.read<int32_t>();
400 const auto movementIndex = stream.read<int32_t>();
401 stream.skip<int32_t>(6); // unused
402
403 stream
404 .read(animDesc.animBlock)
405 .read(animDesc.animIndex);
406
407 const auto ikRuleCount = stream.read<int32_t>();
408 const auto ikRuleIndex = stream.read<int32_t>();
409
410 stream.read(animDesc.animBlockIKRuleIndex);
411
412 const auto localHierarchyCount = stream.read<int32_t>();
413 const auto localHierarchyIndex = stream.read<int32_t>();
414
415 const auto sectionIndex = stream.read<int32_t>();
416 stream.read(animDesc.sectionFrames);
417
418 // TODO: Parse zeroframe bone data at zeroFrameIndex when animBlock != 0
419 // For each bone with BONE_HAS_SAVEFRAME_POS: read zeroFrameCount * Vector48
420 // For each bone with BONE_HAS_SAVEFRAME_ROT: read zeroFrameCount * Quaternion64
421 stream.read(animDesc.zeroFrameSpan)
422 .read(animDesc.zeroFrameCount)
423 .read(animDesc.zeroFrameIndex)
424 .read(animDesc.zeroFrameStallTime);
425
426 // TODO: Load external animations from external file when animBlock != 0
427 // Animation data, IK rules (animBlockIKRuleIndex), and local hierarchy are in external blocks
428 if (animDesc.animIndex != 0 && animDesc.animBlock == 0) {
429 const auto animDataPos = animDescPos + animDesc.animIndex;
430 if (!seekAndValidate(stream, animDataPos)) {
431 return false;
432 }
433 const auto savedPos = stream.tell();
434
435 // LL of mstudioanim_t structures
436 // bone, flags, nextoffset (2 bytes), then var data
437 while (true) {
438 const auto boneAnimPos = stream.tell();
439
440 auto& boneAnim = animDesc.boneAnimations.emplace_back();
441 stream
442 .read(boneAnim.bone)
443 .read(boneAnim.flags);
444
445 if (boneAnim.bone == 255) {
446 animDesc.boneAnimations.pop_back();
447 break;
448 }
449
450 const auto nextOffset = stream.read<int16_t>();
451
452 // flags
453 if (boneAnim.flags & AnimBoneData::FLAG_RAW_ROT2) {
454 boneAnim.staticRotation = stream.read<math::QuatCompressed64>();
455 } else if (boneAnim.flags & AnimBoneData::FLAG_RAW_ROT) {
456 boneAnim.staticRotation = stream.read<math::QuatCompressed48>();
457 }
458
459 if (boneAnim.flags & AnimBoneData::FLAG_RAW_POS) {
460 boneAnim.staticPosition = stream.read<math::Vec3Compressed48>();
461 }
462
463 // rotation
464 if (boneAnim.flags & AnimBoneData::FLAG_ANIM_ROT) {
465 AnimValuePtr rotPtr{};
466 stream.read(rotPtr);
467 boneAnim.animRotationPtr = rotPtr;
468
469 const auto ptrPos = stream.tell();
470
471 // pitch/yaw/roll
472 for (uint8_t idx = 0; idx < rotPtr.size(); idx++) {
473 const auto comp = rotPtr[idx];
474 if (comp > 0) {
475 if (!seekAndValidate(stream, ptrPos - sizeof(AnimValuePtr) + comp)) {
476 return false;
477 }
478 readAnimValueRLE(stream, animDesc.frameCount, boneAnim.animRotationData);
479 }
480 }
481
482 stream.seek_u(ptrPos);
483 }
484
485 // position
486 if (boneAnim.flags & AnimBoneData::FLAG_ANIM_POS) {
487 AnimValuePtr posPtr{};
488 stream.read(posPtr);
489 boneAnim.animPositionPtr = posPtr;
490
491 const auto ptrPos = stream.tell();
492
493 // x/y/z
494 for (uint8_t idx = 0; idx < posPtr.size(); idx++) {
495 const auto comp = posPtr[idx];
496 if (comp > 0) {
497 if (!seekAndValidate(stream, ptrPos - sizeof(AnimValuePtr) + comp)) {
498 return false;
499 }
500 readAnimValueRLE(stream, animDesc.frameCount, boneAnim.animPositionData);
501 }
502 }
503
504 stream.seek_u(ptrPos);
505 }
506
507 if (nextOffset == 0) {
508 break;
509 }
510 if (!seekAndValidate(stream, boneAnimPos + nextOffset)) {
511 return false;
512 }
513 }
514
515 stream.seek_u(savedPos);
516 }
517
518 const auto movementDataPos = animDescPos + movementIndex;
519 if (!seekAndValidate(stream, movementDataPos)) {
520 return false;
521 }
522
523 for (int j = 0; j < movementCount; j++) {
524 auto& movement = animDesc.movements.emplace_back();
525 stream.read(movement.endFrame);
526
527 movement.flags = static_cast<Movement::Flags>(stream.read<int32_t>());
528
529 stream
530 .read(movement.velocityStart)
531 .read(movement.velocityEnd)
532 .read(movement.yawEnd)
533 .read(movement.movement)
534 .read(movement.relativePosition);
535 }
536
537 if (ikRuleIndex != 0 && animDesc.animBlock == 0) {
538 const auto ikRuleDataPos = animDescPos + ikRuleIndex;
539 if (!seekAndValidate(stream, ikRuleDataPos)) {
540 return false;
541 }
542
543 for (int j = 0; j < ikRuleCount; j++) {
544 const auto ikRulePos = ikRuleDataPos + j * (sizeof(int32_t) * 20 + sizeof(float) * 11 + sizeof(math::Vec3f) + sizeof(math::Quat));
545 stream.seek_u(ikRulePos);
546
547 auto& ikRule = animDesc.ikRules.emplace_back();
548
549 stream
550 .read(ikRule.index)
551 .read(ikRule.type)
552 .read(ikRule.chain)
553 .read(ikRule.bone)
554 .read(ikRule.slot)
555 .read(ikRule.height)
556 .read(ikRule.radius)
557 .read(ikRule.floor)
558 .read(ikRule.pos)
559 .read(ikRule.q);
560
561 const auto compressedIKErrorIndex = stream.read<int32_t>();
562 stream.skip<int32_t>(); // unused2
563
564 stream
565 .read(ikRule.iStart);
566
567 const auto ikErrorIndex = stream.read<int32_t>();
568 // note: afaik crowbar has a bug when decompiling 1 frame anims ??
569 // [0.0, 0.0, 1.0, 1.0] should be 0 0 1 1 but crowbar gives 0 0 0 0 (for the model I tested: bot_heavy from TF2)
570 stream
571 .read(ikRule.start)
572 .read(ikRule.peak)
573 .read(ikRule.tail)
574 .read(ikRule.end)
575 .skip<float>() // unused3
576 .read(ikRule.contact)
577 .read(ikRule.drop)
578 .read(ikRule.top)
579 .skip<int32_t>() // unused6
580 .skip<int32_t>() // unused7
581 .skip<int32_t>(); // unused8
582
583 parser::binary::readStringAtOffset(stream, ikRule.attachment);
584
585 stream.skip<int32_t>(7); // unused[7]
586
587 // compressed IK error
588 if (compressedIKErrorIndex != 0) {
589 const auto compErrorPos = ikRulePos + compressedIKErrorIndex;
590 const auto savedPos = stream.tell();
591 stream.seek_u(compErrorPos);
592
593 ikRule.compressedIKError = CompressedIKError{};
594 stream
595 .read(ikRule.compressedIKError->scale)
596 .read(ikRule.compressedIKError->offset);
597
598 const auto compErrorDataPos = stream.tell();
599 for (uint8_t idx = 0; idx < ikRule.compressedIKError->offset.size(); idx++) {
600 const auto k = ikRule.compressedIKError->offset[idx];
601 if (k > 0) {
602 if (!seekAndValidate(stream, compErrorDataPos + k)) {
603 return false;
604 }
605 readAnimValueRLE(stream, animDesc.frameCount, ikRule.compressedIKError->animValues);
606 }
607 }
608
609 stream.seek_u(savedPos);
610 }
611
612 // uncompressed IK error
613 if (ikErrorIndex != 0) {
614 const auto ikErrorPos = ikRulePos + ikErrorIndex;
615 const auto savedPos = stream.tell();
616 stream.seek_u(ikErrorPos);
617
618 const int errorFrameCount = animDesc.frameCount - ikRule.iStart;
619 ikRule.ikErrors.reserve(errorFrameCount);
620
621 for (int k = 0; k < errorFrameCount; k++) {
622 auto& ikError = ikRule.ikErrors.emplace_back();
623 stream
624 .read(ikError.position)
625 .read(ikError.rotation);
626 }
627
628 stream.seek_u(savedPos);
629 }
630 }
631 }
632
633 if (localHierarchyIndex != 0 && animDesc.animBlock == 0) {
634 for (int j = 0; j < localHierarchyCount; j++) {
635 const auto localHierarchyPos = animDescPos + localHierarchyIndex + j * (sizeof(int32_t) * 2 + sizeof(float) * 4 + sizeof(int32_t) * 6);
636 stream.seek_u(localHierarchyPos);
637
638 auto& localHierarchy = animDesc.localHierarchies.emplace_back();
639 stream
640 .read(localHierarchy.bone)
641 .read(localHierarchy.newParent)
642 .read(localHierarchy.start)
643 .read(localHierarchy.peak)
644 .read(localHierarchy.tail)
645 .read(localHierarchy.end)
646 .read(localHierarchy.startFrame);
647
648 const auto localAnimIndex = stream.read<int32_t>();
649 stream.skip<int32_t>(4); // unused[4]
650
651 if (localAnimIndex != 0) {
652 const auto compErrorPos = localHierarchyPos + localAnimIndex;
653 stream.seek_u(compErrorPos);
654
655 localHierarchy.compressedIKError = CompressedIKError{};
656 stream
657 .read(localHierarchy.compressedIKError->scale)
658 .read(localHierarchy.compressedIKError->offset);
659
660 const auto compErrorDataPos = stream.tell();
661 for (uint8_t idx = 0; idx < localHierarchy.compressedIKError->offset.size(); idx++) {
662 const auto k = localHierarchy.compressedIKError->offset[idx];
663 if (k > 0) {
664 if (!seekAndValidate(stream, compErrorDataPos + k)) {
665 return false;
666 }
667 readAnimValueRLE(stream, animDesc.frameCount, localHierarchy.compressedIKError->animValues);
668 }
669 }
670 }
671 }
672 }
673
674 if (sectionIndex != 0 && animDesc.sectionFrames > 0) {
675 // NOTE: numsections is not stored in the file, only in studiomdl's internal s_animation_t.
676 // So I cannot guarantee this formula is correct - cueki
677 const int sectionCount = animDesc.frameCount / animDesc.sectionFrames + 2;
678 for (int j = 0; j < sectionCount; j++) {
679 const auto sectionPos = animDescPos + sectionIndex + j * sizeof(AnimSection);
680 stream.seek_u(sectionPos);
681
682 auto& section = animDesc.sections.emplace_back();
683 stream
684 .read(section.animBlock)
685 .read(section.animIndex);
686 }
687 }
688 }
689
690 stream.seek(sequenceDescOffset);
691 for (int i = 0; i < sequenceDescCount; i++) {
692 const auto sequenceDescPos = sequenceDescOffset + i * (sizeof(int32_t) * 38 + sizeof(float) * 9 + sizeof(math::Vec3f) * 2);
693 stream.seek_u(sequenceDescPos);
694
695 auto& sequenceDesc = this->sequences.emplace_back();
696
697 stream.skip<int32_t>(); // basePointer
698 parser::binary::readStringAtOffset(stream, sequenceDesc.label, std::ios::cur, 8);
699 parser::binary::readStringAtOffset(stream, sequenceDesc.activityName, std::ios::cur, 12);
700 stream
701 .read(sequenceDesc.flags)
702 .read(sequenceDesc.activity)
703 .read(sequenceDesc.activityWeight);
704
705 const auto eventCount = stream.read<int32_t>();
706 const auto eventIndex = stream.read<int32_t>();
707
708 stream
709 .read(sequenceDesc.boundingBoxMin)
710 .read(sequenceDesc.boundingBoxMax)
711 .read(sequenceDesc.blendCount);
712
713 const auto animIndexIndex = stream.read<int32_t>();
714
715 stream
716 .read(sequenceDesc.movementIndex)
717 .read(sequenceDesc.groupSize)
718 .read(sequenceDesc.paramIndex)
719 .read(sequenceDesc.paramStart)
720 .read(sequenceDesc.paramEnd)
721 .read(sequenceDesc.paramParent)
722 .read(sequenceDesc.fadeInTime)
723 .read(sequenceDesc.fadeOutTime)
724 .read(sequenceDesc.localEntryNode)
725 .read(sequenceDesc.localExitNode)
726 .read(sequenceDesc.nodeFlags)
727 .read(sequenceDesc.entryPhase)
728 .read(sequenceDesc.exitPhase)
729 .read(sequenceDesc.lastFrame)
730 .read(sequenceDesc.nextSequence)
731 .read(sequenceDesc.pose)
732 .read(sequenceDesc.ikRuleCount);
733
734 const auto autoLayerCount = stream.read<int32_t>();
735 const auto autoLayerIndex = stream.read<int32_t>();
736
737 const auto weightListIndex = stream.read<int32_t>();
738 const auto poseKeyIndex = stream.read<int32_t>();
739
740 const auto ikLockCount = stream.read<int32_t>();
741 const auto ikLockIndex = stream.read<int32_t>();
742
743 const auto seqKeyValueIndex = stream.read<int32_t>();
744 const auto seqKeyValueSize = stream.read<int32_t>();
745
746 stream.read(sequenceDesc.cyclePoseIndex);
747
748 const auto activityModifierIndex = stream.read<int32_t>();
749 const auto activityModifierCount = stream.read<int32_t>();
750
751 stream.skip<int32_t>(5); // unused[5]
752
753 if (animIndexIndex != 0 && sequenceDesc.groupSize[0] > 0 && sequenceDesc.groupSize[1] > 0) {
754 stream.seek_u(sequenceDescPos + animIndexIndex);
755 const int blendCount = sequenceDesc.groupSize[0] * sequenceDesc.groupSize[1];
756 for (int j = 0; j < blendCount; j++) {
757 sequenceDesc.animIndices.push_back(stream.read<int16_t>());
758 }
759 }
760
761 // if boneCount is not set this will be garbage data so we check for it
762 if (weightListIndex != 0 && boneCount > 0) {
763 stream.seek_u(sequenceDescPos + weightListIndex);
764 for (int j = 0; j < boneCount; j++) {
765 sequenceDesc.boneWeights.push_back(stream.read<float>());
766 }
767 }
768
769 if (poseKeyIndex != 0) {
770 stream.seek_u(sequenceDescPos + poseKeyIndex);
771 // pPoseKey(iParam, iAnim) indexes as iParam * groupsize[0] + iAnim
772 // iParam is 0 or 1 (2 params), iAnim is 0 to groupsize[0]-1
773 // So total keys = 2 * groupSize[0]
774 const int poseKeyCount = 2 * sequenceDesc.groupSize[0];
775 for (int j = 0; j < poseKeyCount; j++) {
776 sequenceDesc.poseKeys.push_back(stream.read<float>());
777 }
778 }
779
780 if (activityModifierIndex != 0) {
781 for (int j = 0; j < activityModifierCount; j++) {
782 const auto modifierPos = sequenceDescPos + activityModifierIndex + j * sizeof(int32_t);
783 stream.seek_u(modifierPos);
784 auto& modifier = sequenceDesc.activityModifiers.emplace_back();
785 parser::binary::readStringAtOffset(stream, modifier);
786 }
787 }
788
789 const auto ikLockDataPos = sequenceDescPos + ikLockIndex;
790 stream.seek_u(ikLockDataPos);
791
792 for (int j = 0; j < ikLockCount; j++) {
793 auto& ikLock = sequenceDesc.ikLocks.emplace_back();
794 stream
795 .read(ikLock.chain)
796 .read(ikLock.posWeight)
797 .read(ikLock.localQWeight)
798 .read(ikLock.flags);
799 stream.skip<int32_t>(4); // unused[4]
800 }
801
802 const auto eventDataPos = sequenceDescPos + eventIndex;
803 stream.seek_u(eventDataPos);
804
805 for (int j = 0; j < eventCount; j++) {
806 auto& event = sequenceDesc.events.emplace_back();
807
808 stream
809 .read(event.cycle)
810 .read(event.event)
811 .read(event.type)
812 .read(event.options, 64);
813
814 parser::binary::readStringAtOffset(stream, event.eventName, std::ios::cur,
815 sizeof(float) + sizeof(int32_t) * 2 + 64);
816 }
817
818 const auto autoLayerDataPos = sequenceDescPos + autoLayerIndex;
819 stream.seek_u(autoLayerDataPos);
820
821 for (int j = 0; j < autoLayerCount; j++) {
822 auto& autoLayer = sequenceDesc.autoLayers.emplace_back();
823 stream
824 .read(autoLayer.sequence)
825 .read(autoLayer.pose)
826 .read(autoLayer.flags)
827 .read(autoLayer.start)
828 .read(autoLayer.peak)
829 .read(autoLayer.tail)
830 .read(autoLayer.end);
831 }
832
833 if (seqKeyValueSize > 0 && seqKeyValueIndex != 0) {
834 const auto seqKeyValueDataPos = sequenceDescPos + seqKeyValueIndex;
835 stream.seek_u(seqKeyValueDataPos).read(sequenceDesc.keyValues, seqKeyValueSize);
836 }
837 }
838
839 stream.seek(materialOffset);
840 for (int i = 0; i < materialCount; i++) {
841 auto& material = this->materials.emplace_back();
842 parser::binary::readStringAtOffset(stream, material.name);
843 stream.read(material.flags);
844
845 // used
846 stream.skip<int32_t>();
847 // _unused0
848 stream.skip<int32_t>(13);
849 }
850
851 stream.seek(materialDirOffset);
852 for (int i = 0; i < materialDirCount; i++) {
853 auto& materialDir = this->materialDirectories.emplace_back();
854 parser::binary::readStringAtOffset(stream, materialDir, std::ios::beg, 0);
855 }
856
857 stream.seek(skinReferenceOffset);
858 for (int i = 0; i < skinReferenceFamilyCount; i++) {
859 std::vector<int16_t> skinFamily;
860 skinFamily.reserve(skinReferenceCount);
861 for (int j = 0; j < skinReferenceCount; j++) {
862 skinFamily.push_back(stream.read<int16_t>());
863 }
864 this->skins.push_back(std::move(skinFamily));
865 }
866
867 for (int i = 0; i < bodyPartCount; i++) {
868 const auto bodyPartPos = bodyPartOffset + i * (sizeof(int32_t) * 4);
869 stream.seek_u(bodyPartPos);
870
871 auto& bodyPart = this->bodyParts.emplace_back();
872 parser::binary::readStringAtOffset(stream, bodyPart.name);
873
874 const auto modelsCount = stream.read<int32_t>();
875 stream.read(bodyPart.base);
876 const auto modelsOffset = stream.read<int32_t>();
877
878 for (int j = 0; j < modelsCount; j++) {
879 auto modelPos = modelsOffset + j * (64 + sizeof(float) + sizeof(int32_t) * 20);
880 if (!seekAndValidate(stream, bodyPartPos + modelPos)) {
881 return false;
882 }
883
884 auto& model = bodyPart.models.emplace_back();
885
886 stream
887 .read(model.name, 64)
888 .read(model.type)
889 .read(model.boundingRadius);
890
891 const auto meshesCount = stream.read<int32_t>();
892 const auto meshesOffset = stream.read<int32_t>();
893
894 stream
895 .read(model.verticesCount)
896 .read(model.verticesOffset)
897 .read(model.tangentsOffset);
898
899 const auto modelAttachmentsCount = stream.read<int32_t>();
900 const auto modelAttachmentsOffset = stream.read<int32_t>();
901
902 const auto eyeballsCount = stream.read<int32_t>();
903 const auto eyeballsOffset = stream.read<int32_t>();
904
905 for (int k = 0; k < meshesCount; k++) {
906 const auto meshPos = meshesOffset + k * (sizeof(int32_t) * (18 + MAX_LOD_COUNT) + sizeof(math::Vec3f));
907 if (!seekAndValidate(stream, bodyPartPos + modelPos + meshPos)) {
908 return false;
909 }
910
911 auto& mesh = model.meshes.emplace_back();
912
913 stream
914 .read(mesh.material)
915 .skip<int32_t>()
916 .read(mesh.verticesCount)
917 .read(mesh.verticesOffset);
918
919 const auto meshFlexCount = stream.read<int32_t>();
920 const auto meshFlexOffset = stream.read<int32_t>();
921
922 stream
923 .read(mesh.materialType)
924 .read(mesh.materialParam)
925 .read(mesh.meshID)
926 .read(mesh.center)
927 .read(mesh.modelVertexData)
928 .read(mesh.numLODVertexes);
929
930 stream.skip<int32_t>(8); // unused
931
932 if (meshFlexOffset != 0) {
933
934 for (int m = 0; m < meshFlexCount; m++) {
935 const auto flexPos = meshFlexOffset + m * (sizeof(int32_t) * 10 + sizeof(float) * 4 + sizeof(uint8_t) * 4);
936 if (!seekAndValidate(stream, bodyPartPos + modelPos + meshPos + flexPos)) {
937 return false;
938 }
939
940 auto& flex = mesh.flexes.emplace_back();
941
942 stream
943 .read(flex.flexDescIndex)
944 .read(flex.target0)
945 .read(flex.target1)
946 .read(flex.target2)
947 .read(flex.target3);
948
949 const auto numverts = stream.read<int32_t>();
950 const auto vertindex = stream.read<int32_t>();
951
952 stream
953 .read(flex.flexPair)
954 .read(flex.vertAnimType);
955
956 if (vertindex != 0) {
957 // vertindex is relative to the flex structure base
958 const auto vertAnimPos = bodyPartPos + modelPos + meshPos + flexPos + vertindex;
959 if (!seekAndValidate(stream, vertAnimPos)) {
960 return false;
961 }
962
963 if (flex.vertAnimType == 0) {
964 // normal vertex animations
965 flex.vertAnims.reserve(numverts);
966 for (int n = 0; n < numverts; n++) {
967 auto& vertAnim = flex.vertAnims.emplace_back();
968 stream
969 .read(vertAnim.index)
970 .read(vertAnim.speed)
971 .read(vertAnim.side)
972 .read(vertAnim.delta)
973 .read(vertAnim.ndelta);
974 }
975 } else if (flex.vertAnimType == 1) {
976 // wrinkle vertex animations
977 flex.vertAnimsWrinkle.reserve(numverts);
978 for (int n = 0; n < numverts; n++) {
979 auto& vertAnim = flex.vertAnimsWrinkle.emplace_back();
980 stream
981 .read(vertAnim.index)
982 .read(vertAnim.speed)
983 .read(vertAnim.side)
984 .read(vertAnim.delta)
985 .read(vertAnim.ndelta)
986 .read(vertAnim.wrinkleDelta);
987 }
988 }
989 }
990 }
991 }
992 }
993
994 if (eyeballsOffset != 0) {
995 for (int k = 0; k < eyeballsCount; k++) {
996 const auto eyeballPos = eyeballsOffset + k * (sizeof(int32_t) * 24 + sizeof(float) * 9 + sizeof(math::Vec3f) * 3 + sizeof(uint8_t) * 4);
997 if (!seekAndValidate(stream, bodyPartPos + modelPos + eyeballPos)) {
998 return false;
999 }
1000
1001 auto& eyeball = model.eyeballs.emplace_back();
1002
1003 // note: eyeball names are not stored in compiled MDL files ?
1004 // sznameindex exists in the struct but is never populated by studiomdl
1005 stream.skip<int32_t>(); // sznameindex
1006
1007 stream
1008 .read(eyeball.bone)
1009 .read(eyeball.org)
1010 .read(eyeball.zOffset)
1011 .read(eyeball.radius)
1012 .read(eyeball.up)
1013 .read(eyeball.forward)
1014 .read(eyeball.texture)
1015 .skip<int32_t>() // unused1
1016 .read(eyeball.irisScale)
1017 .skip<int32_t>() // unused2
1018 .read(eyeball.upperFlexDesc)
1019 .read(eyeball.lowerFlexDesc)
1020 .read(eyeball.upperTarget)
1021 .read(eyeball.lowerTarget)
1022 .read(eyeball.upperLidFlexDesc)
1023 .read(eyeball.lowerLidFlexDesc);
1024
1025 stream.skip<int32_t>(4); // unused[4]
1026 stream.read(eyeball.nonFACS);
1027 stream.skip<uint8_t>(3); // unused3[3]
1028 stream.skip<int32_t>(7); // unused4[7]
1029 }
1030 }
1031
1032 if (modelAttachmentsOffset != 0) {
1033 for (int k = 0; k < modelAttachmentsCount; k++) {
1034 const auto attachmentPos = modelAttachmentsOffset + k * (sizeof(int32_t) + sizeof(uint32_t) + sizeof(int32_t) + sizeof(math::Mat3x4f) + sizeof(int32_t) * 8);
1035 if (!seekAndValidate(stream, bodyPartPos + modelPos + attachmentPos)) {
1036 return false;
1037 }
1038
1039 auto& attachment = model.attachments.emplace_back();
1040 parser::binary::readStringAtOffset(stream, attachment.name);
1041
1042 stream
1043 .skip<uint32_t>() // flags
1044 .read(attachment.bone)
1045 .read(attachment.localMatrix);
1046
1047 // extract position from the last column of the matrix
1048 attachment.position[0] = attachment.localMatrix[0][3];
1049 attachment.position[1] = attachment.localMatrix[1][3];
1050 attachment.position[2] = attachment.localMatrix[2][3];
1051
1052 stream.skip<int32_t>(8); // unused
1053 }
1054 }
1055 }
1056 }
1057
1058 stream.seek(attachmentOffset);
1059 for (int i = 0; i < attachmentCount; i++) {
1060 auto& attachment = this->attachments.emplace_back();
1061 parser::binary::readStringAtOffset(stream, attachment.name);
1062
1063 stream
1064 .skip<int32_t>() // flags
1065 .read(attachment.bone)
1066 .read(attachment.localMatrix);
1067
1068 // extract position from the last column of the matrix
1069 attachment.position[0] = attachment.localMatrix[0][3];
1070 attachment.position[1] = attachment.localMatrix[1][3];
1071 attachment.position[2] = attachment.localMatrix[2][3];
1072
1073 stream.skip<int32_t>(8); // unused
1074 }
1075
1076 if (localNodeCount > 0) {
1077 stream.seek(localNodeNameIndex);
1078 this->localNodeNames.reserve(localNodeCount);
1079 for (int i = 0; i < localNodeCount; i++) {
1080 auto& nodeName = this->localNodeNames.emplace_back();
1081 parser::binary::readStringAtOffset(stream, nodeName, std::ios::beg, 0);
1082 }
1083
1084 // transition matrix (localNodeCount × localNodeCount bytes)
1085 stream.seek(localNodeIndex);
1086 const auto transitionMatrixSize = localNodeCount * localNodeCount;
1087 this->localNodeTransitions.resize(transitionMatrixSize);
1088 for (int i = 0; i < transitionMatrixSize; i++) {
1089 this->localNodeTransitions[i] = stream.read<uint8_t>();
1090 }
1091 }
1092
1093 if (flexDescCount > 0) {
1094 stream.seek(flexDescIndex);
1095 for (int i = 0; i < flexDescCount; i++) {
1096 auto& flexDescName = this->flexDescs.emplace_back();
1097 parser::binary::readStringAtOffset(stream, flexDescName);
1098 }
1099 }
1100
1101 for (int i = 0; i < flexControllerCount; i++) {
1102 const auto flexControllerPos = flexControllerIndex + i * (sizeof(int32_t) * 3 + sizeof(float) * 2);
1103 stream.seek_u(flexControllerPos);
1104
1105 auto& flexController = this->flexControllers.emplace_back();
1106
1107 // lambda to read at relative offset
1108 auto readStringAtRelativeOffset = [&](std::string& target) {
1109 if (const auto offset = stream.read<int32_t>(); offset != 0) {
1110 const auto savedPos = stream.tell();
1111 // skips if fail
1112 if (!seekAndValidate(stream, flexControllerPos + offset)) {
1113 return;
1114 }
1115 stream.read(target);
1116 stream.seek_u(savedPos);
1117 }
1118 };
1119
1120 readStringAtRelativeOffset(flexController.type);
1121 readStringAtRelativeOffset(flexController.name);
1122
1123 stream
1124 .read(flexController.localToGlobal)
1125 .read(flexController.min)
1126 .read(flexController.max);
1127 }
1128
1129 for (int i = 0; i < flexRulesCount; i++) {
1130 const auto flexRulePos = flexRulesIndex + i * (sizeof(int32_t) * 3);
1131 stream.seek_u(flexRulePos);
1132
1133 auto& flexRule = this->flexRules.emplace_back();
1134 stream.read(flexRule.flex);
1135
1136 const auto opCount = stream.read<int32_t>();
1137 const auto opIndex = stream.read<int32_t>();
1138
1139 const auto opDataPos = flexRulePos + opIndex;
1140 stream.seek_u(opDataPos);
1141
1142 for (int j = 0; j < opCount; j++) {
1143 auto& op = flexRule.ops.emplace_back();
1144 stream
1145 .read(op.op)
1146 .read(op.d.index);
1147 }
1148 }
1149
1150 for (int i = 0; i < ikChainCount; i++) {
1151 const auto ikChainPos = ikChainIndex + i * (sizeof(int32_t) * 4);
1152 stream.seek_u(ikChainPos);
1153
1154 auto& ikChain = this->ikChains.emplace_back();
1155 parser::binary::readStringAtOffset(stream, ikChain.name);
1156 stream.read(ikChain.linkType);
1157
1158 const auto linkCount = stream.read<int32_t>();
1159 const auto linkIndex = stream.read<int32_t>();
1160
1161 const auto linkDataPos = ikChainPos + linkIndex;
1162 stream.seek_u(linkDataPos);
1163
1164 for (int j = 0; j < linkCount; j++) {
1165 auto& link = ikChain.links.emplace_back();
1166 stream
1167 .read(link.bone)
1168 .read(link.kneeDir)
1169 .skip<math::Vec3f>(); // unused
1170 }
1171 }
1172
1173 if (mouthsCount > 0) {
1174 stream.seek(mouthsIndex);
1175 for (int i = 0; i < mouthsCount; i++) {
1176 auto& mouth = this->mouths.emplace_back();
1177 stream
1178 .read(mouth.bone)
1179 .read(mouth.forward)
1180 .read(mouth.flexDescIndex);
1181 }
1182 }
1183
1184 if (localPoseParamCount > 0) {
1185 stream.seek(localPoseParamIndex);
1186
1187 for (int i = 0; i < localPoseParamCount; i++) {
1188 auto& poseParam = this->poseParameters.emplace_back();
1189 parser::binary::readStringAtOffset(stream, poseParam.name);
1190 stream
1191 .read(poseParam.flags)
1192 .read(poseParam.start)
1193 .read(poseParam.end)
1194 .read(poseParam.loop);
1195 }
1196 }
1197
1198 if (surfacePropertyIndex != 0) {
1199 stream.seek(surfacePropertyIndex).read(this->surfaceProperty);
1200 }
1201
1202 if (keyValueCount > 0) {
1203 stream.seek(keyValueIndex).read(this->keyValues, keyValueCount);
1204 }
1205
1206 if (localIKAutoplayLockCount > 0) {
1207 stream.seek(localIKAutoplayLockIndex);
1208 for (int i = 0; i < localIKAutoplayLockCount; i++) {
1209 auto& ikLock = this->ikAutoplayLocks.emplace_back();
1210 stream
1211 .read(ikLock.chain)
1212 .read(ikLock.posWeight)
1213 .read(ikLock.localQWeight)
1214 .read(ikLock.flags);
1215 stream.skip<int32_t>(4); // unused[4]
1216 }
1217 }
1218
1219 for (int i = 0; i < includeModelCount; i++) {
1220 const auto includeModelPos = includeModelIndex + i * (sizeof(int32_t) * 2);
1221 stream.seek_u(includeModelPos);
1222 auto& includeModel = this->includeModels.emplace_back();
1223
1224 // lambda to read at relative offset
1225 auto readStringAtRelativeOffset = [&](std::string& target) {
1226 if (const auto offset = stream.read<int32_t>(); offset != 0) {
1227 const auto savedPos = stream.tell();
1228 // skips if fail
1229 if (!seekAndValidate(stream, includeModelPos + offset)) {
1230 return;
1231 }
1232 stream.read(target);
1233 stream.seek_u(savedPos);
1234 }
1235 };
1236
1237 readStringAtRelativeOffset(includeModel.label);
1238 readStringAtRelativeOffset(includeModel.name);
1239 }
1240
1241 if (animationBlocksNameIndex != 0) {
1242 stream.seek(animationBlocksNameIndex).read(this->animationBlocksName);
1243 }
1244
1245 if (animationBlocksCount > 0) {
1246 stream.seek(animationBlocksIndex);
1247 for (int i = 0; i < animationBlocksCount; i++) {
1248 auto& animBlock = this->animationBlocks.emplace_back();
1249 stream
1250 .read(animBlock.dataStart)
1251 .read(animBlock.dataEnd);
1252 }
1253 }
1254
1255 if (boneTableNameIndex != 0) {
1256 stream.seek(boneTableNameIndex);
1257 this->boneTableByName.reserve(boneCount);
1258 for (int i = 0; i < boneCount; i++) {
1259 this->boneTableByName.push_back(stream.read<uint8_t>());
1260 }
1261 }
1262
1263 for (int i = 0; i < flexControllerUICount; i++) {
1264 const auto flexControllerUIPos = flexControllerUIIndex + i * (sizeof(int32_t) * 4 + sizeof(uint8_t) * 4);
1265 stream.seek_u(flexControllerUIPos);
1266
1267 auto& flexControllerUI = this->flexControllerUIs.emplace_back();
1268 parser::binary::readStringAtOffset(stream, flexControllerUI.name);
1269
1270 const auto szIndex0 = stream.read<int32_t>();
1271 const auto szIndex1 = stream.read<int32_t>();
1272 const auto szIndex2 = stream.read<int32_t>();
1273
1274 stream
1275 .read(flexControllerUI.remapType)
1276 .read(flexControllerUI.stereo);
1277
1278 stream.skip<uint8_t>(2); // unused
1279
1280 // lambda to resolve controller name from szIndex offset
1281 auto resolveControllerName = [&](int32_t szIndex, std::string& targetName) {
1282 if (szIndex != 0) {
1283 const auto savedPos = stream.tell();
1284 stream.seek_u(flexControllerUIPos + szIndex);
1285 stream.skip<int32_t>();
1286 if (const auto controllerNameOffset = stream.read<int32_t>(); controllerNameOffset != 0) {
1287 // skips if fail
1288 if (!seekAndValidate(stream, flexControllerUIPos + szIndex + controllerNameOffset)) {
1289 return;
1290 }
1291 stream.read(targetName);
1292 }
1293 stream.seek_u(savedPos);
1294 }
1295 };
1296
1297 resolveControllerName(szIndex0, flexControllerUI.controllerName0);
1298 resolveControllerName(szIndex1, flexControllerUI.controllerName1);
1299 resolveControllerName(szIndex2, flexControllerUI.controllerName2);
1300 }
1301
1302 return true;
1303}
constexpr int32_t MDL_ID
Definition MDL.cpp:10
sourcepp::math::Vec3i16 AnimValuePtr
Definition Generic.h:75
constexpr int MAX_LOD_COUNT
Definition Generic.h:11
Vec4f Quat
Definition Math.h:364
void readStringAtOffset(BufferStream &stream, std::string &str, std::ios::seekdir offsetFrom=std::ios::cur, std::size_t subtractFromOffset=sizeof(int32_t))
Reads an integer from the stream, seeks there, reads a string, and seeks back.
Definition Binary.cpp:7
consteval uint32_t makeFourCC(const char fourCC[4])
Creates a FourCC identifier from a string of 4 characters.
Definition Binary.h:20
std::vector< PoseParameter > poseParameters
Definition MDL.h:794
sourcepp::math::Vec3f viewBBoxMax
Definition MDL.h:718
std::vector< FlexRule > flexRules
Definition MDL.h:782
sourcepp::math::Vec3f hullMax
Definition MDL.h:716
uint8_t directionalDotProduct
Definition MDL.h:830
std::vector< Material > materials
Definition MDL.h:747
std::vector< std::string > localNodeNames
Definition MDL.h:769
std::vector< AnimDesc > animations
Definition MDL.h:736
int32_t activityListVersion
Definition MDL.h:742
std::vector< Mouth > mouths
Definition MDL.h:790
Flags flags
Definition MDL.h:720
bool open(const std::byte *data, std::size_t size)
Definition MDL.cpp:51
bool hasHeader2
Definition MDL.h:860
std::vector< std::string > flexDescs
Definition MDL.h:774
std::string name
Definition MDL.h:710
std::string keyValues
Definition MDL.h:801
std::vector< IKChain > ikChains
Definition MDL.h:786
std::vector< Attachment > attachments
Definition MDL.h:764
sourcepp::math::Vec3f viewBBoxMin
Definition MDL.h:717
sourcepp::math::Vec3f eyePosition
Definition MDL.h:713
int32_t studioHdr2Index
Definition MDL.h:844
std::vector< FlexControllerUI > flexControllerUIs
Definition MDL.h:839
std::vector< SequenceDesc > sequences
Definition MDL.h:740
sourcepp::math::Vec3f hullMin
Definition MDL.h:715
std::vector< std::vector< int16_t > > skins
Definition MDL.h:756
std::vector< std::string > materialDirectories
Definition MDL.h:751
int32_t checksum
Definition MDL.h:707
std::string surfaceProperty
Definition MDL.h:797
uint8_t rootLOD
Definition MDL.h:831
std::vector< IncludeModel > includeModels
Definition MDL.h:812
std::vector< uint8_t > boneTableByName
Definition MDL.h:825
std::vector< SrcBoneTransform > srcBoneTransforms
Definition MDL.h:862
sourcepp::math::Vec3f illuminationPosition
Definition MDL.h:714
std::vector< BodyPart > bodyParts
Definition MDL.h:760
std::vector< BoneFlexDriver > boneFlexDrivers
Definition MDL.h:864
uint8_t numAllowedRootLODs
Definition MDL.h:832
std::vector< AnimBlock > animationBlocks
Definition MDL.h:820
std::vector< BoneController > boneControllers
Definition MDL.h:728
std::vector< uint8_t > localNodeTransitions
Definition MDL.h:770
std::vector< FlexController > flexControllers
Definition MDL.h:778
std::vector< Bone > bones
Definition MDL.h:724
std::optional< LinearBone > linearBone
Definition MDL.h:863
std::string animationBlocksName
Definition MDL.h:819
float mass
Definition MDL.h:807
int32_t eventsIndexed
Definition MDL.h:743
int32_t contentsFlags
Definition MDL.h:808
Header2 header2
Definition MDL.h:859
std::vector< HitboxSet > hitboxSets
Definition MDL.h:732
float vertAnimFixedPointScale
Definition MDL.h:841
std::vector< IKLock > ikAutoplayLocks
Definition MDL.h:805
Lower precision Quat compressed to 6 bytes.
Definition Math.h:367
Lower precision Quat compressed to 8 bytes.
Definition Math.h:393
Lower precision Vec3 compressed to 6 bytes.
Definition Math.h:419
int16_t value
Definition Generic.h:70
uint8_t valid
Definition Generic.h:67
uint8_t total
Definition Generic.h:68
struct mdlpp::AnimValue::@260325364302223141001101027371236243335257207016 num