forked from OpenKotOR/mdledit
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbinaryread.cpp
More file actions
1741 lines (1524 loc) · 101 KB
/
Copy pathbinaryread.cpp
File metadata and controls
1741 lines (1524 loc) · 101 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#include "MDL.h"
#include <limits>
#include <algorithm>
#include <utility>
/**
Functions:
MDL::LinearizeGeometry()
MDL::LinearizeAnimation()
MDL::DecompileModel()
MDL::ParseAabb()
MDL::ParseNode()
*/
bool bReadSmoothing = false;
namespace {
unsigned int MdlDataOffset(unsigned int relativeOffset, const std::string & context){
if(relativeOffset > std::numeric_limits<unsigned int>::max() - MDL_OFFSET){
throw mdlexception(context + " offset is outside the MDL data buffer.");
}
return static_cast<unsigned int>(MDL_OFFSET + relativeOffset);
}
unsigned int MdlArrayDataOffset(const ArrayHead & array, const std::string & context){
if(array.nCount != array.nCount2){
throw mdlexception(context + " array header count fields do not match; refusing to guess which count is authoritative.");
}
if(array.nCount == 0){
return 0;
}
if(array.nOffset == 0){
throw mdlexception(context + " array has a non-zero count but a zero data offset; refusing to read data from the wrong region.");
}
return MdlDataOffset(array.nOffset, context);
}
unsigned int RequiredMdlDataOffset(unsigned int relativeOffset, const std::string & context){
if(relativeOffset == 0){
throw mdlexception(context + " offset is zero even though data is required; refusing to read from the wrong region.");
}
return MdlDataOffset(relativeOffset, context);
}
MdlInteger<unsigned short> SignedIntToUShortOrInvalid(int nValue, const std::string & context){
if(nValue == -1) return MdlInteger<unsigned short>();
if(nValue < -1){
throw mdlexception(context + " contains a negative value other than -1; refusing to reinterpret it as a valid 16-bit index.");
}
const int nInvalid = static_cast<int>(std::numeric_limits<unsigned short>::max());
if(nValue >= nInvalid){
throw mdlexception(context + " is outside the valid 16-bit MDL index range.");
}
return MdlInteger<unsigned short>(static_cast<unsigned short>(nValue));
}
std::string DebugCStringAt(const std::vector<char> & buffer, unsigned offset){
if(offset >= buffer.size()) return std::string("<out of range>");
auto begin = buffer.begin() + offset;
auto end = std::find(begin, buffer.end(), '\0');
return std::string(begin, end);
}
template <class F>
class ScopeExit{
F fn;
bool active = true;
public:
explicit ScopeExit(F f) : fn(std::move(f)) {}
ScopeExit(const ScopeExit&) = delete;
ScopeExit& operator=(const ScopeExit&) = delete;
ScopeExit(ScopeExit && other) : fn(std::move(other.fn)), active(other.active) { other.active = false; }
ScopeExit& operator=(ScopeExit &&) = delete;
void Dismiss(){ active = false; }
~ScopeExit(){ if(active) fn(); }
};
template <class F>
ScopeExit<F> MakeScopeExit(F f){
return ScopeExit<F>(std::move(f));
}
template <class T>
void ResizeBinaryVector(std::vector<T> & values, unsigned int nCount, const std::string & context){
if(static_cast<std::size_t>(nCount) > values.max_size()){
throw mdlexception(context + " count is too large to allocate safely.");
}
values.resize(static_cast<std::size_t>(nCount));
}
unsigned int BwmArrayOffset(unsigned int nCount, unsigned int nOffset, unsigned int nStride, std::size_t nBufferSize, const std::string & context){
if(nCount == 0) return 0;
if(nOffset == 0){
throw mdlexception(context + " has a non-zero count but a zero data offset; refusing to read data from the header/unknown region.");
}
if(nStride != 0 && nCount > std::numeric_limits<unsigned int>::max() / nStride){
throw mdlexception(context + " byte size overflows 32-bit BWM offsets.");
}
const std::size_t nBytes = static_cast<std::size_t>(nCount) * static_cast<std::size_t>(nStride);
if(static_cast<std::size_t>(nOffset) > nBufferSize || nBytes > nBufferSize - static_cast<std::size_t>(nOffset)){
throw mdlexception(context + " data range points outside the binary buffer; refusing to allocate or read malformed data.");
}
return nOffset;
}
unsigned short MaxNameIndexInTree(const Node & node){
if(!node.Head.nNameIndex.Valid()){
throw mdlexception("Binary node tree contains a node with an invalid name index.");
}
unsigned short nMax = static_cast<unsigned short>(node.Head.nNameIndex);
for(const Node & child : node.Head.Children){
if(child.nOffset == 0) continue;
const unsigned short nChildMax = MaxNameIndexInTree(child);
if(nChildMax > nMax) nMax = nChildMax;
}
return nMax;
}
void EnsureFlattenedNodeSlotAvailable(const std::vector<Node> & nodes, unsigned short nNameIndex){
if(nNameIndex >= nodes.size()){
throw mdlexception("LinearizeGeometry() error: node name index is outside the flattened node array.");
}
if(nodes.at(nNameIndex).Head.nNameIndex.Valid() || nodes.at(nNameIndex).Head.nType != 0 || nodes.at(nNameIndex).nOffset != 0){
throw mdlexception("LinearizeGeometry() error: duplicate geometry node name index would overwrite an existing node.");
}
}
}
/// Linearize the nodes from being contained inside one another to the ArrayOfNodes in Name Index order.
void MDL::LinearizeGeometry(Node & node, std::vector<Node> & ArrayOfNodes){
if(!node.Head.nNameIndex.Valid()){
throw mdlexception("LinearizeGeometry() error: node name index is invalid.");
}
const unsigned short nNameIndex = static_cast<unsigned short>(node.Head.nNameIndex);
if(nNameIndex >= ArrayOfNodes.size()){
throw mdlexception("LinearizeGeometry() error: node name index is outside the flattened node array.");
}
// Detach the parsed child tree before moving this node into the flat output.
// The flat model uses ChildIndices for hierarchy; keeping moved-from child
// vectors in the flattened nodes wastes memory and can expose stale state.
std::vector<Node> children = std::move(node.Head.Children);
node.Head.Children.clear();
for(Node & child : children){
if(child.nOffset != 0) LinearizeGeometry(child, ArrayOfNodes);
}
// Check at assignment time too, so a descendant or sibling with the same
// name index cannot be silently overwritten.
EnsureFlattenedNodeSlotAvailable(ArrayOfNodes, nNameIndex);
ArrayOfNodes.at(nNameIndex) = std::move(node);
}
/// Linearize the nodes from being contained inside one another to the ArrayOfNodes in Node Index order (root first, then first child, its first child, etc.).
void MDL::LinearizeAnimation(Node & node, std::vector<Node> & ArrayOfNodes, int nChildIndex){
(void)nChildIndex;
// Do not recurse through references inside ArrayOfNodes after push_back().
// Appending descendants can reallocate the vector and invalidate those
// references. Detach children first and recurse over the stable local tree.
std::vector<Node> children = std::move(node.Head.Children);
node.Head.Children.clear();
ArrayOfNodes.push_back(std::move(node));
for(unsigned int n = 0; n < children.size(); n++){
Node & child = children.at(n);
if(child.nOffset != 0) LinearizeAnimation(child, ArrayOfNodes, n);
}
}
/// This function will read the binary model.
/// When bMinimal is true, the algorithm will not read data such as the animations and will not output most of the debugging messages.
void MDL::DecompileModel(bool bMinimal){
if(sBuffer.empty()) return;
std::unique_ptr<FileHeader> pNewFileData(new FileHeader());
std::unique_ptr<FileHeader> pOldFileData = std::move(FH);
const ModelSource oldSource = src;
const bool oldK2 = bK2;
const bool oldXbox = bXbox;
const int oldSupermodel = nSupermodel;
const bool oldReadSmoothing = bReadSmoothing;
const BufferState oldBufferState = CaptureBufferState();
FH = std::move(pNewFileData);
src = BinarySource;
auto restoreReadState = MakeScopeExit([&](){
RestoreBufferState(oldBufferState);
FH = std::move(pOldFileData);
src = oldSource;
bK2 = oldK2;
bXbox = oldXbox;
nSupermodel = oldSupermodel;
bReadSmoothing = oldReadSmoothing;
});
ReportObject ReportMdl (*this);
/// Start timer
Timer tDecompile;
nPosition = 0; /// Set reading position to beginning of file.
if(!bMinimal) ReportMdl << "Begin decompiling ";
Report("Decompiling...");
FileHeader & Data = *FH;
//std::cout << "Data ready.\n";
std::string sFileHeader = "File Header";
//First read the file header, geometry header and model header
ReadNumber(&Data.nZero, 8, sFileHeader + " > Padding");
//std::cout << "Read first value, pos " << nPosition << ".\n";
ReadNumber(&Data.nMdlLength, 1, sFileHeader + " > MDL File Size");
ReadNumber(&Data.nMdxLength, 1, sFileHeader + " > MDX File Size");
MarkDataBorder(nPosition - 1);
//if(!bMinimal) ReportMdl << "File header read, pos " << nPosition << ".\n";
std::string sGeometryHeader = "Geometry Header";
ReadNumber(&Data.MH.GH.nFunctionPointer0, 9, sGeometryHeader + " > Function Pointers");
ReadNumber(&Data.MH.GH.nFunctionPointer1, 9, sGeometryHeader + " > Function Pointers");
//std::cout << "Read function pointers, now pos is: " << nPosition << ".\n";
//Get game and platform
//std::cout << "Function pointer 0: "<< Data.MH.GH.nFunctionPointer0 <<".\n";
if(Data.MH.GH.nFunctionPointer0 == FN_PTR_PC_K1_MODEL_1) bK2 = false, bXbox = false;
else if(Data.MH.GH.nFunctionPointer0 == FN_PTR_PC_K2_MODEL_1) bK2 = true, bXbox = false;
else if(Data.MH.GH.nFunctionPointer0 == FN_PTR_XBOX_K2_MODEL_1) bK2 = true, bXbox = true;
else if(Data.MH.GH.nFunctionPointer0 == FN_PTR_XBOX_K1_MODEL_1) bK2 = false, bXbox = true;
else throw mdlexception("Cannot interpret model function pointer (" + std::to_string(Data.MH.GH.nFunctionPointer0) + "), so cannot determine game and platform.");
//std::cout << "Determined game.\n";
ReadString(&Data.MH.GH.sName, 32, 3, sGeometryHeader + " > Name");
if(!bMinimal) ReportMdl << Data.MH.GH.sName.c_str() << ".\n";
ReadNumber(&Data.MH.GH.nOffsetToRootNode, 6, sGeometryHeader + " > Offset to Root Node");
ReadNumber(&Data.MH.GH.nTotalNumberOfNodes, 1, sGeometryHeader + " > Number of Nodes");
ReadNumber(&Data.MH.GH.RuntimeArray1.nOffset, 8, sGeometryHeader + " > Runtime Arrays");
ReadNumber(&Data.MH.GH.RuntimeArray1.nCount, 8, sGeometryHeader + " > Runtime Arrays");
ReadNumber(&Data.MH.GH.RuntimeArray1.nCount2, 8, sGeometryHeader + " > Runtime Arrays");
ReadNumber(&Data.MH.GH.RuntimeArray2.nOffset, 8, sGeometryHeader + " > Runtime Arrays");
ReadNumber(&Data.MH.GH.RuntimeArray2.nCount, 8, sGeometryHeader + " > Runtime Arrays");
ReadNumber(&Data.MH.GH.RuntimeArray2.nCount2, 8, sGeometryHeader + " > Runtime Arrays");
ReadNumber(&Data.MH.GH.nRefCount, 8, sGeometryHeader + " > Reference Count");
ReadNumber(&Data.MH.GH.nModelType, 7, sGeometryHeader + " > Type");
ReadNumber(&Data.MH.GH.nPadding[0], 11, sGeometryHeader + " > Padding");
ReadNumber(&Data.MH.GH.nPadding[1], 11, sGeometryHeader + " > Padding");
ReadNumber(&Data.MH.GH.nPadding[2], 11, sGeometryHeader + " > Padding");
MarkDataBorder(nPosition - 1);
//if(!bMinimal) ReportMdl << "Geometry header read.\n";
std::string sModelHeader = "Model Header";
ReadNumber(&Data.MH.nClassification, 7, sModelHeader + " > Classification");
ReadNumber(&Data.MH.nSubclassification, 10, sModelHeader + " > Unknown1");
ReadNumber(&Data.MH.nUnknown, 8, sModelHeader + " > Unknown1");
ReadNumber(&Data.MH.nAffectedByFog, 7, sModelHeader + " > Affected By Fog");
/// If the proper flags are on, enable SG reading
bReadSmoothing = (bWriteSmoothing && Data.MH.nUnknown == 1) ? true : false;
ReadNumber(&Data.MH.nChildModelCount, 8, sModelHeader + " > Number of Child Models");
ReadNumber(&Data.MH.AnimationArray.nOffset, 6, sModelHeader + " > Offset to Animation Array");
ReadNumber(&Data.MH.AnimationArray.nCount, 1, sModelHeader + " > Number of Animations");
ReadNumber(&Data.MH.AnimationArray.nCount2, 1, sModelHeader + " > Number of Animations");
ReadNumber(&Data.MH.nSupermodelReference, 11, sModelHeader + " > Supermodel Reference");
Data.MH.vBBmin.fX = ReadNumber<float>(nullptr, 2, sModelHeader + " > Bounding Box Min");
Data.MH.vBBmin.fY = ReadNumber<float>(nullptr, 2, sModelHeader + " > Bounding Box Min");
Data.MH.vBBmin.fZ = ReadNumber<float>(nullptr, 2, sModelHeader + " > Bounding Box Min");
Data.MH.vBBmax.fX = ReadNumber<float>(nullptr, 2, sModelHeader + " > Bounding Box Max");
Data.MH.vBBmax.fY = ReadNumber<float>(nullptr, 2, sModelHeader + " > Bounding Box Max");
Data.MH.vBBmax.fZ = ReadNumber<float>(nullptr, 2, sModelHeader + " > Bounding Box Max");
Data.MH.fRadius = ReadNumber<float>(nullptr, 2, sModelHeader + " > Radius");
Data.MH.fScale = ReadNumber<float>(nullptr, 2, sModelHeader + " > Animation Scale");
ReadString(&Data.MH.cSupermodelName, 32, 3, sModelHeader + " > Supermodel Name");
ReadNumber(&Data.MH.nOffsetToHeadRootNode, 6, sModelHeader + " > Offset to Head Root");
ReadNumber(&Data.MH.nPadding, 8, sModelHeader + " > Padding");
ReadNumber(&Data.MH.nMdxLength2, 1, sModelHeader + " > MDX File Size");
ReadNumber(&Data.MH.nOffsetIntoMdx, 8, sModelHeader + " > MDX Data Offset");
ReadNumber(&Data.MH.NameArray.nOffset, 6, sModelHeader + " > Offset to Name Array");
ReadNumber(&Data.MH.NameArray.nCount, 1, sModelHeader + " > Number of Names");
ReadNumber(&Data.MH.NameArray.nCount2, 1, sModelHeader + " > Number of Names");
MarkDataBorder(nPosition - 1);
//if(!bMinimal) ReportMdl << "Model header read.\n";
/// The header is fully done!
/// Now we're equipped to disassemble the rest
/// First index names array
//if(!bMinimal) ReportMdl << "Reading names.\n";
if(Data.MH.NameArray.nCount > 0){
std::string sNameArrayPointers = "Name Array > Pointers > Pointer ";
std::string sNameArrayStrings = "Name Array > Strings > \"";
Data.MH.Names.resize(Data.MH.NameArray.nCount);
nPosition = MdlArrayDataOffset(Data.MH.NameArray, "name array");
for(unsigned int n = 0; n < Data.MH.NameArray.nCount; n++){
ReadNumber(&Data.MH.Names[n].nOffset, 6, sNameArrayPointers + std::to_string(n));
MarkDataBorder(nPosition - 1);
unsigned nPosData = RequiredMdlDataOffset(Data.MH.Names[n].nOffset, "name string");
ReadString(&Data.MH.Names[n].sName, 0, 3, sNameArrayStrings + DebugCStringAt(GetBuffer(), nPosData) + "\"", &nPosData);
MarkDataBorder(nPosData - 1);
}
}
//if(!bMinimal) ReportMdl << "Name array read.\n";
Report("Decompiling animations...");
//if(!bMinimal) ReportMdl << "Reading animations.\n";
/// Next, animations. Skip them if we're reading minimally
if(Data.MH.AnimationArray.nCount > 0 && !bMinimal){
Data.MH.Animations.resize(Data.MH.AnimationArray.nCount);
nPosition = MdlArrayDataOffset(Data.MH.AnimationArray, "animation array");
unsigned nAnimationPointerPosition = nPosition;
for(unsigned int n = 0; n < Data.MH.AnimationArray.nCount; n++){
nPosition = nAnimationPointerPosition;
std::string sAnimationPointer = "Animations > Pointers > Pointer" + std::to_string(n);
Animation & anim = Data.MH.Animations.at(n);
ReadNumber(&anim.nOffset, 6, sAnimationPointer);
MarkDataBorder(nPosition - 1);
nAnimationPointerPosition = nPosition;
nPosition = RequiredMdlDataOffset(anim.nOffset, "animation header");
std::string sAnimation = "Animations > " + DebugCStringAt(GetBuffer(), nPosition + 8) + " > ";
std::string sAnimationGeometryHeader = sAnimation + "Geometry Header";
ReadNumber(&anim.nFunctionPointer0, 9, sAnimationGeometryHeader + " > Function Pointers");
ReadNumber(&anim.nFunctionPointer1, 9, sAnimationGeometryHeader + " > Function Pointers");
ReadString(&anim.sName, 32, 3, sAnimationGeometryHeader + " > Name");
ReadNumber(&anim.nOffsetToRootAnimationNode, 6, sAnimationGeometryHeader + " > Offset to Root Node");
ReadNumber(&anim.nNumberOfNames, 1, sAnimationGeometryHeader + " > Number of Nodes");
ReadNumber(&anim.RuntimeArray1.nOffset, 8, sAnimationGeometryHeader + " > Runtime Arrays");
ReadNumber(&anim.RuntimeArray1.nCount, 8, sAnimationGeometryHeader + " > Runtime Arrays");
ReadNumber(&anim.RuntimeArray1.nCount2, 8, sAnimationGeometryHeader + " > Runtime Arrays");
ReadNumber(&anim.RuntimeArray2.nOffset, 8, sAnimationGeometryHeader + " > Runtime Arrays");
ReadNumber(&anim.RuntimeArray2.nCount, 8, sAnimationGeometryHeader + " > Runtime Arrays");
ReadNumber(&anim.RuntimeArray2.nCount2, 8, sAnimationGeometryHeader + " > Runtime Arrays");
ReadNumber(&anim.nRefCount, 8, sAnimationGeometryHeader + " > Reference Count");
ReadNumber(&anim.nModelType, 7, sAnimationGeometryHeader + " > Type");
ReadNumber(&anim.nPadding[0], 11, sAnimationGeometryHeader + " > Padding");
ReadNumber(&anim.nPadding[1], 11, sAnimationGeometryHeader + " > Padding");
ReadNumber(&anim.nPadding[2], 11, sAnimationGeometryHeader + " > Padding");
MarkDataBorder(nPosition - 1);
anim.fLength = ReadNumber<float>(nullptr, 2, sAnimation + "Header");
anim.fTransition = ReadNumber<float>(nullptr, 2, sAnimation + "Header");
ReadString(&anim.sAnimRoot, 32, 3, sAnimation + "Header");
ReadNumber(&anim.EventArray.nOffset, 6, sAnimation + "Header");
ReadNumber(&anim.EventArray.nCount, 1, sAnimation + "Header");
ReadNumber(&anim.EventArray.nCount2, 1, sAnimation + "Header");
ReadNumber(&anim.nPadding2, 8, sAnimation + "Header");
MarkDataBorder(nPosition - 1);
if(anim.EventArray.nCount > 0){
anim.Events.resize(anim.EventArray.nCount);
nPosition = MdlArrayDataOffset(anim.EventArray, "animation event array"); /// No need to save the previous position because we've finished the header
//std::cout << string_format("Offset to Animation Events is %i\n", anim.EventArray.nOffset);
for(int e = 0; e < static_cast<int>(anim.Events.size()); e++){
Event & event = anim.Events.at(e);
event.fTime = ReadNumber<float>(nullptr, 2, sAnimation + "Header");
ReadString(&event.sName, 32, 3, sAnimation + "Header");
MarkDataBorder(nPosition - 1);
}
}
/// We're done with the header, now we delve into animation nodes. It's a bit scary :(
/// Prepare root node
if(anim.nOffsetToRootAnimationNode == 0){
throw mdlexception("Animation '" + anim.sName + "' has a zero root-node offset; refusing to read the animation header as a node.");
}
anim.RootAnimationNode.nOffset = anim.nOffsetToRootAnimationNode;
anim.RootAnimationNode.nAnimation = n;
/// Prepare for parsing and parse
std::vector<unsigned int> offsets;
offsets.reserve(Data.MH.nNodeCount);
Vector vFromRoot;
ParseNode(anim.RootAnimationNode, offsets, vFromRoot);
/// Prepare for linearization and linearize
anim.ArrayOfNodes.clear();
anim.ArrayOfNodes.reserve(Data.MH.Names.size());
LinearizeAnimation(anim.RootAnimationNode, anim.ArrayOfNodes, -1);
}
}
//if(!bMinimal) ReportMdl << "Animation array read.\n";
Report("Decompiling geometry...");
//if(!bMinimal) ReportMdl << "Reading geometry.\n";
if(Data.MH.Names.size() > 0){
/// Set offset and animation
if(Data.MH.GH.nOffsetToRootNode == 0){
throw mdlexception("Geometry root-node offset is zero; refusing to read the geometry header as a node.");
}
Data.MH.RootNode.nOffset = Data.MH.GH.nOffsetToRootNode;
Data.MH.RootNode.nAnimation = -1;
/// Prepare variables for parsing nodes and then parse the nodes.
std::vector<unsigned int> offsets;
offsets.reserve(Data.MH.nNodeCount);
Vector vFromRoot;
ParseNode(Data.MH.RootNode, offsets, vFromRoot, bMinimal);
/// Record total real node count
Data.MH.nNodeCount = offsets.size();
//std::cout << string_format("Node count for the Geometry: %i, compared to the number in the header, %i.\n", nNodeCounter, Data.MH.GH.nNumberOfNodes);
//Data.MH.ArrayOfNodes.clear();
/// Linearize read geometry into an Array Of Nodes
const MdlInteger<unsigned short> nRootNameIndex = Data.MH.RootNode.Head.nNameIndex;
if(!nRootNameIndex.Valid()){
throw mdlexception("Binary read root node name index is invalid.");
}
const unsigned short nMaxGeometryNameIndex = MaxNameIndexInTree(Data.MH.RootNode);
const std::size_t nFlattenedNodeCount = std::max<std::size_t>(Data.MH.nNodeCount, static_cast<std::size_t>(nMaxGeometryNameIndex) + 1u);
Data.MH.ArrayOfNodes.clear();
Data.MH.ArrayOfNodes.resize(nFlattenedNodeCount);
LinearizeGeometry(Data.MH.RootNode, Data.MH.ArrayOfNodes);
/// Build Array of Indices By Tree Order
if(Data.MH.ArrayOfNodes.empty()){
throw mdlexception("Binary read produced no geometry nodes.");
}
if(static_cast<unsigned short>(nRootNameIndex) >= Data.MH.ArrayOfNodes.size() ||
!Data.MH.ArrayOfNodes.at(static_cast<unsigned short>(nRootNameIndex)).Head.nNameIndex.Valid()){
throw mdlexception("Binary read root node name index is outside the flattened node array.");
}
Data.MH.NameIndicesInTreeOrder.reserve(Data.MH.ArrayOfNodes.size());
Data.MH.BuildTreeOrderArray(Data.MH.ArrayOfNodes.at(static_cast<unsigned short>(nRootNameIndex)));
/// Immediately fix all the skin bone->name maps
for(Node & node : Data.MH.ArrayOfNodes){
if(node.Head.nType & NODE_SKIN){
int nMaxBonemapSlot = -1;
for(unsigned int n = 0; n < node.Skin.Bones.size(); n++){
Bone & bone = node.Skin.Bones.at(n);
if(bone.nBonemap.Valid()){
const unsigned int nSlot = bone.nBonemap;
const unsigned int nMaxCompactSlot = bK2 ? 16u : 15u;
if(nSlot > nMaxCompactSlot){
throw mdlexception("Skin bone map slot is outside the compact-slot range for this game.");
}
if(nSlot > static_cast<unsigned int>(std::numeric_limits<int>::max())){
throw mdlexception("Skin bone map slot is too large.");
}
if(static_cast<int>(nSlot) > nMaxBonemapSlot) nMaxBonemapSlot = static_cast<int>(nSlot);
}
}
node.Skin.BoneNameIndices.clear();
if(nMaxBonemapSlot >= 0){
node.Skin.BoneNameIndices.resize(static_cast<std::size_t>(nMaxBonemapSlot) + 1u);
}
for(unsigned int n = 0; n < node.Skin.Bones.size(); n++){
if(n >= Data.MH.NameIndicesInTreeOrder.size()){
throw mdlexception("Skin bone table references a node outside the tree-order name table.");
}
Bone & bone = node.Skin.Bones.at(n);
if(bone.nBonemap.Valid()){
const unsigned int nSlot = bone.nBonemap;
if(nSlot >= node.Skin.BoneNameIndices.size()){
throw mdlexception("Skin bone map slot is outside the compact bone-name table.");
}
node.Skin.BoneNameIndices.at(nSlot) = Data.MH.NameIndicesInTreeOrder.at(n);
}
bone.nNameIndex = Data.MH.NameIndicesInTreeOrder.at(n);
}
}
}
}
//if(!bMinimal) ReportMdl << "Geometry read.\n";
/// Here we'll go around and fix all the animation node numbers to match the geometry nodes.
for(Animation & anim : Data.MH.Animations){
/// Fix the name indices in node, name indices in controllers and child indices.
for(Node & anim_node : anim.ArrayOfNodes){
for(Node & geom_node : Data.MH.ArrayOfNodes){
/// When you find the corresponding geom node, copy the name index
if(anim_node.Head.nSupernodeNumber == geom_node.Head.nSupernodeNumber){
if(anim_node.Head.nNameIndex != geom_node.Head.nNameIndex){
/// We also need to correct it in all the controllers
for(Controller & ctrl : anim_node.Head.Controllers){
ctrl.nNameIndex = geom_node.Head.nNameIndex;
}
/// We need to find the parent node to change the child index
for(Node & anim_node2 : anim.ArrayOfNodes) if(anim_node2.Head.nSupernodeNumber == anim_node.Head.nParentIndex){
for(auto & child_ind : anim_node2.Head.ChildIndices) if(child_ind == anim_node.Head.nNameIndex)
{
child_ind = geom_node.Head.nNameIndex;
break;
}
break;
}
/// Finally, change the name index on the node itself
anim_node.Head.nNameIndex = geom_node.Head.nNameIndex;
}
break;
}
}
}
/// Fix parent indices.
for(Node & anim_node : anim.ArrayOfNodes){
for(Node & geom_node : Data.MH.ArrayOfNodes){
/// When you find the corresponding parent geom node, copy the name index
/// Q: why is the parent index set to the supernode number at this point?
/// A: because otherwise how the heck are we gonna know that its the right one
if(anim_node.Head.nParentIndex == geom_node.Head.nSupernodeNumber){
if(anim_node.Head.nParentIndex != geom_node.Head.nNameIndex){
anim_node.Head.nParentIndex = geom_node.Head.nNameIndex;
}
break;
}
}
}
/// Only now can we record the child indices // This is now done above already
/*
for(Node & anim_node : anim.ArrayOfNodes){
anim_node.Head.ChildIndices.clear();
for(Node & child : anim_node.Head.Children){
anim_node.Head.ChildIndices.push_back(child.Head.nNameIndex);
}
}
*/
}
if(!bMinimal) ReportMdl << "Decompiled model in " << tDecompile.GetTime() << ".\n";
if(!bReadSmoothing && bDetermineSmoothing && Mdx && !bMinimal) DetermineSmoothing();
restoreReadState.Dismiss();
}
static unsigned nAabbCount = 0;
static std::string sAabbNodePrefix;
void MDL::ParseAabb(Aabb & aabb, std::vector<unsigned int> & visitedOffsets){
std::string sAabb = sAabbNodePrefix + "Data > Aabb > Aabb Tree > Aabb Struct " + std::to_string(nAabbCount);
nAabbCount++;
if(aabb.nOffset == 0) throw mdlexception("An aabb node has offset 0; refusing to read the MDL header as AABB data.");
if(aabb.nOffset == std::numeric_limits<unsigned int>::max()) throw mdlexception("An aabb node has offset -1.");
if(std::find(visitedOffsets.begin(), visitedOffsets.end(), aabb.nOffset) != visitedOffsets.end()){
throw mdlexception("The aabb (walkmesh) tree loops or reuses child offset " + std::to_string(aabb.nOffset) + "; refusing to duplicate or malform the preserved tree.");
}
visitedOffsets.push_back(aabb.nOffset);
unsigned int nPosData = MdlDataOffset(aabb.nOffset, "AABB node");
aabb.vBBmin.fX = ReadNumber<float>(nullptr, 2, sAabb, &nPosData);
aabb.vBBmin.fY = ReadNumber<float>(nullptr, 2, sAabb, &nPosData);
aabb.vBBmin.fZ = ReadNumber<float>(nullptr, 2, sAabb, &nPosData);
aabb.vBBmax.fX = ReadNumber<float>(nullptr, 2, sAabb, &nPosData);
aabb.vBBmax.fY = ReadNumber<float>(nullptr, 2, sAabb, &nPosData);
aabb.vBBmax.fZ = ReadNumber<float>(nullptr, 2, sAabb, &nPosData);
ReadNumber(&aabb.nChild1, 6, sAabb, &nPosData);
ReadNumber(&aabb.nChild2, 6, sAabb, &nPosData);
aabb.nID = SignedIntToUShortOrInvalid(ReadNumber<signed int>(nullptr, 4, sAabb, &nPosData), sAabb + " face id");
aabb.nProperty = ReadNumber<unsigned>(nullptr, 4, sAabb, &nPosData);
MarkDataBorder(nPosData - 1);
if(aabb.nChild1.Valid() && aabb.nChild1 > 0){
aabb.Child1.resize(1);
aabb.Child1[0].nOffset = aabb.nChild1;
ParseAabb(aabb.Child1[0], visitedOffsets);
}
if(aabb.nChild2.Valid() && aabb.nChild2 > 0){
aabb.Child2.resize(1);
aabb.Child2[0].nOffset = aabb.nChild2;
ParseAabb(aabb.Child2[0], visitedOffsets);
}
}
void MDL::ParseNode(Node & node, std::vector<unsigned int> & offsets, Vector vFromRoot, bool bMinimal){
if(node.nOffset == 0) throw mdlexception("ParseNode(): node offset is 0; refusing to read the MDL header as a node.");
if(node.nOffset == std::numeric_limits<unsigned int>::max()) throw mdlexception("ParseNode(): node offset is -1; refusing to read invalid node data.");
nPosition = RequiredMdlDataOffset(node.nOffset, "node header");
unsigned int nPosData = 0;
offsets.push_back(node.nOffset);
if(nPosition > sBuffer.size() || 4 + sizeof(unsigned short) > sBuffer.size() - nPosition){
throw mdlexception("ParseNode(): node header is truncated before the name index field.");
}
unsigned short nNodeNameIndex = 0;
std::memcpy(&nNodeNameIndex, &sBuffer.at(nPosition + 4), sizeof(nNodeNameIndex));
if(nNodeNameIndex >= GetFileData()->MH.Names.size()){
throw mdlexception("ParseNode(): node name index " + std::to_string(nNodeNameIndex) +
" is outside the model name table; refusing to read from the wrong name/string data.");
}
std::string sNodeName = std::string(GetFileData()->MH.Names.at(nNodeNameIndex).sName.c_str());
std::string sNodePrefix = "Geometry > ";
if(node.nAnimation.Valid() && static_cast<unsigned>(node.nAnimation) < GetFileData()->MH.Animations.size()){
sNodePrefix = "Animations > " + GetFileData()->MH.Animations.at(static_cast<unsigned>(node.nAnimation)).sName + " > ";
}
std::string sNode = sNodePrefix + sNodeName + " > ";
ReadNumber(node.Head.nType.GetPtr(), 5, sNode + "Node Type");
ReadNumber(node.Head.nSupernodeNumber.GetPtr(), 5, sNode + "Supernode Number");
ReadNumber(node.Head.nNameIndex.GetPtr(), 5, sNode + "Node Number");
ReadNumber(&node.Head.nPadding1, 8, sNode + (node.nAnimation.Valid() ? "Header" : "Header > Basic"));
ReportObject ReportMdl (*this);
//ReportMdl << "Reading " << (node.nAnimation.Valid() ? "animation" : "geometry") << " node " << GetNodeName(node) << " at offset " << nPosition - 8 << ".\n";
if(!(node.Head.nType & NODE_HEADER)) throw mdlexception(std::string("The ") + (node.nAnimation.Valid() ? "animation" : "geometry") + " node " + GetNodeName(node) + " does not have a NODE_HEADER.");
ReadNumber(node.Head.nOffsetToRoot.GetPtr(), 6, sNode + "Offset to Root");
ReadNumber(node.Head.nOffsetToParent.GetPtr(), 6, sNode + "Offset to Parent");
node.Head.vPos.fX = ReadNumber<float>(nullptr, 2, sNode + "Position");
node.Head.vPos.fY = ReadNumber<float>(nullptr, 2, sNode + "Position");
node.Head.vPos.fZ = ReadNumber<float>(nullptr, 2, sNode + "Position");
double fQW = ReadNumber<float>(nullptr, 2, sNode + "Orientation");
double fQX = ReadNumber<float>(nullptr, 2, sNode + "Orientation");
double fQY = ReadNumber<float>(nullptr, 2, sNode + "Orientation");
double fQZ = ReadNumber<float>(nullptr, 2, sNode + "Orientation");
node.Head.oOrient.SetQuaternion(fQX, fQY, fQZ, fQW);
ReadNumber(&node.Head.ChildrenArray.nOffset, 6, sNode + "Child Array");
ReadNumber(&node.Head.ChildrenArray.nCount, 1, sNode + "Child Array");
ReadNumber(&node.Head.ChildrenArray.nCount2, 1, sNode + "Child Array");
ReadNumber(&node.Head.ControllerArray.nOffset, 6, sNode + "Controller Array");
ReadNumber(&node.Head.ControllerArray.nCount, 1, sNode + "Controller Array");
ReadNumber(&node.Head.ControllerArray.nCount2, 1, sNode + "Controller Array");
ReadNumber(&node.Head.ControllerDataArray.nOffset, 6, sNode + "Controller Data Array");
ReadNumber(&node.Head.ControllerDataArray.nCount, 1, sNode + "Controller Data Array");
ReadNumber(&node.Head.ControllerDataArray.nCount2, 1, sNode + "Controller Data Array");
MarkDataBorder(nPosition - 1);
if(node.Head.ControllerDataArray.nCount > 0 && !bMinimal){
/// We gots controll data!
node.Head.ControllerData.resize(node.Head.ControllerDataArray.nCount);
nPosData = MdlArrayDataOffset(node.Head.ControllerDataArray, "controller data array");
for(unsigned int n = 0; n < node.Head.ControllerDataArray.nCount; n++){
std::string sControllerData = sNode + (node.nAnimation.Valid() ? "Controller Data > Float " : "Data > Controller Data > Float ") + std::to_string(n);
node.Head.ControllerData[n] = ReadNumber<float>(nullptr, 2, sControllerData, &nPosData);
MarkDataBorder(nPosData - 1);
//if(n == node.Head.ControllerDataArray.nCount) std::cout << string_format("Just filled %i floats of Controller Data\n", n);
}
}
if(node.Head.ControllerArray.nCount > 0 && !bMinimal){
node.Head.Controllers.resize(node.Head.ControllerArray.nCount);
nPosData = MdlArrayDataOffset(node.Head.ControllerArray, "controller array");
for(unsigned int n = 0; n < node.Head.ControllerArray.nCount; n++){
Controller & ctrl = node.Head.Controllers.at(n);
std::string sController = sNode + (node.nAnimation.Valid() ? "Controllers > Controller " : "Data > Controllers > Controller ") + std::to_string(n);
ReadNumber(&ctrl.nControllerType, 4, sController, &nPosData);
ReadNumber(&ctrl.nUnknown2, 10, sController, &nPosData);
ReadNumber(&ctrl.nValueCount, 5, sController, &nPosData);
ReadNumber(&ctrl.nTimekeyStart, 5, sController, &nPosData);
ReadNumber(&ctrl.nDataStart, 5, sController, &nPosData);
ReadNumber(&ctrl.nColumnCount, 5, sController, &nPosData);
ReadNumber(&ctrl.nPadding[0], 11, sController, &nPosData);
ReadNumber(&ctrl.nPadding[1], 11, sController, &nPosData);
ReadNumber(&ctrl.nPadding[2], 11, sController, &nPosData);
MarkDataBorder(nPosData - 1);
ctrl.nNameIndex = node.Head.nNameIndex;
ctrl.nAnimation = node.nAnimation;
if(ctrl.nControllerType == CONTROLLER_HEADER_ORIENTATION && node.nAnimation.Valid()){
if(ctrl.nColumnCount == 2 && !FH->MH.bCompressQuaternions) FH->MH.bCompressQuaternions = true;
else if(ctrl.nColumnCount != 2 && FH->MH.bCompressQuaternions) FH->MH.bCompressQuaternions = false;
}
int nCount = ctrl.nColumnCount & 0x0F;
if(ctrl.nControllerType == CONTROLLER_HEADER_ORIENTATION && nCount == 2) nCount = 1;
if(ctrl.nColumnCount & 0x10) nCount *= 3;
const std::size_t nColumns = static_cast<std::size_t>(std::max(nCount, 0));
const std::size_t nValues = static_cast<std::size_t>(ctrl.nValueCount);
const std::size_t nStart = static_cast<std::size_t>(ctrl.nDataStart);
bool bMissingControllerData = false;
if(nValues != 0 && nColumns > (std::numeric_limits<std::size_t>::max() - nStart) / nValues){
bMissingControllerData = true;
}
else if(nStart + nColumns * nValues > node.Head.ControllerData.size()){
bMissingControllerData = true;
}
if(bMissingControllerData){
std::string sAnimName = "<geometry>";
if(node.nAnimation.Valid() && static_cast<std::size_t>(static_cast<unsigned int>(node.nAnimation)) < FH->MH.Animations.size()){
sAnimName = FH->MH.Animations.at(static_cast<unsigned int>(node.nAnimation)).sName;
}
Warning("Missing controller data on a controller on node '" + GetNodeName(node) + "' in animation '" + sAnimName + "'.");
}
}
}
/// Calculate transforms after controller data has been read: rotate the
/// incoming offset by the current node orientation, then add the current
/// node position.
Location location = node.GetLocation();
Quaternion qNode = location.oOrientation.GetQuaternion();
vFromRoot.Rotate(qNode);
vFromRoot += location.vPosition;
node.Head.vFromRoot = vFromRoot;
unsigned nSavePosition = nPosition;
if(node.Head.ChildrenArray.nCount > 0){
/// We gots children!
node.Head.Children.resize(node.Head.ChildrenArray.nCount);
node.Head.ChildIndices.clear();
nPosData = MdlArrayDataOffset(node.Head.ChildrenArray, "child array");
for(int n = 0; n < static_cast<int>(node.Head.Children.size()); n++){
Node & child = node.Head.Children.at(n);
std::string sChildPointer = sNode + (node.nAnimation.Valid() ? "Child Pointers > Pointer " : "Data > Child Array > Pointer ") + std::to_string(n);
ReadNumber(&child.nOffset, 6, sChildPointer, &nPosData);
MarkDataBorder(nPosData - 1);
child.nAnimation = node.nAnimation;
/// If this is a geometry node, then the name index is reliable and we may use it for the parent index.
/// If however this is an animation node, the name index is not reliable, so we will put in the supernode number instead
/// and then use it later to find the correct name index.
child.Head.nParentIndex = !node.nAnimation.Valid() ? node.Head.nNameIndex : node.Head.nSupernodeNumber;
if(child.nOffset == 0 || child.nOffset == std::numeric_limits<unsigned int>::max()){
ReportMdl << "Warning: node '" << GetNodeName(node) << "' has an invalid child pointer ("
<< child.nOffset << "); ignoring that child entry.\n";
child.nOffset = 0;
}
else if(std::find(offsets.begin(), offsets.end(), child.nOffset) == offsets.end()){
ParseNode(child, offsets, vFromRoot, bMinimal);
}
else{
/**
Okay, so it seems that the offset of our child has already been parsed. This means that there is a loop in the model.
I will solve this loop now by not parsing this child. So this will break the loop, but it will leave this unread child behind.
It is now the job of the linearization functions to ignore this child. I will mark this child by setting its offset to 0.
So, when the offset of a node is 0, linearization of that node should not take place.
*/
child.nOffset = 0;
}
if(child.nOffset != 0 && child.Head.nNameIndex.Valid()){
node.Head.ChildIndices.push_back(child.Head.nNameIndex); /// Sorting based on child indices? Does that even work for animations?
}
}
}
/// If we only want to read minimally, we're done at this point
if(bMinimal) return;
/// Return the position to where we were before doing all the children in between
nPosition = nSavePosition;
if(node.Head.nType & NODE_LIGHT){
std::string sLight = sNode.substr(0, sNode.size() - 3) + " > Header > Light";
node.Light.fFlareRadius = ReadNumber<float>(nullptr, 2, sLight);
ReadNumber(&node.Light.UnknownArray.nOffset, 8, sLight);
ReadNumber(&node.Light.UnknownArray.nCount, 8, sLight);
ReadNumber(&node.Light.UnknownArray.nCount2, 8, sLight);
ReadNumber(&node.Light.FlareSizeArray.nOffset, 6, sLight);
ReadNumber(&node.Light.FlareSizeArray.nCount, 1, sLight);
ReadNumber(&node.Light.FlareSizeArray.nCount2, 1, sLight);
ReadNumber(&node.Light.FlarePositionArray.nOffset, 6, sLight);
ReadNumber(&node.Light.FlarePositionArray.nCount, 1, sLight);
ReadNumber(&node.Light.FlarePositionArray.nCount2, 1, sLight);
ReadNumber(&node.Light.FlareColorShiftArray.nOffset, 6, sLight);
ReadNumber(&node.Light.FlareColorShiftArray.nCount, 1, sLight);
ReadNumber(&node.Light.FlareColorShiftArray.nCount2, 1, sLight);
ReadNumber(&node.Light.FlareTextureNameArray.nOffset, 6, sLight);
ReadNumber(&node.Light.FlareTextureNameArray.nCount, 1, sLight);
ReadNumber(&node.Light.FlareTextureNameArray.nCount2, 1, sLight);
ReadNumber(node.Light.nLightPriority.GetPtr(), 4, sLight);
ReadNumber(node.Light.nAmbientOnly.GetPtr(), 4, sLight);
ReadNumber(node.Light.nDynamicType.GetPtr(), 4, sLight);
ReadNumber(node.Light.nAffectDynamic.GetPtr(), 4, sLight);
ReadNumber(node.Light.nShadow.GetPtr(), 4, sLight);
ReadNumber(node.Light.nFlare.GetPtr(), 4, sLight);
ReadNumber(node.Light.nFadingLight.GetPtr(), 4, sLight);
MarkDataBorder(nPosition - 1);
if(node.Light.FlareTextureNameArray.nCount > 0){
node.Light.FlareTextureNames.resize(node.Light.FlareTextureNameArray.nCount);
nPosData = MdlArrayDataOffset(node.Light.FlareTextureNameArray, "light flare texture-name array");
for(unsigned int n = 0; n < node.Light.FlareTextureNameArray.nCount; n++){
ReadNumber(&node.Light.FlareTextureNames[n].nOffset, 6, sLight, &nPosData);
MarkDataBorder(nPosData - 1);
unsigned nPosData2 = RequiredMdlDataOffset(node.Light.FlareTextureNames[n].nOffset, "light flare texture name");
ReadString(&node.Light.FlareTextureNames[n].sName, 0, 3, sLight, &nPosData2);
}
}
if(node.Light.FlareSizeArray.nCount > 0){
node.Light.FlareSizes.resize(node.Light.FlareSizeArray.nCount);
nPosData = MdlArrayDataOffset(node.Light.FlareSizeArray, "light flare size array");
for(unsigned int n = 0; n < node.Light.FlareSizeArray.nCount; n++){
node.Light.FlareSizes[n] = ReadNumber<float>(nullptr, 2, sLight, &nPosData);
MarkDataBorder(nPosData - 1);
}
}
if(node.Light.FlarePositionArray.nCount > 0){
node.Light.FlarePositions.resize(node.Light.FlarePositionArray.nCount);
nPosData = MdlArrayDataOffset(node.Light.FlarePositionArray, "light flare position array");
for(unsigned int n = 0; n < node.Light.FlarePositionArray.nCount; n++){
node.Light.FlarePositions[n] = ReadNumber<float>(nullptr, 2, sLight, &nPosData);
MarkDataBorder(nPosData - 1);
}
}
if(node.Light.FlareColorShiftArray.nCount > 0){
node.Light.FlareColorShifts.resize(node.Light.FlareColorShiftArray.nCount);
nPosData = MdlArrayDataOffset(node.Light.FlareColorShiftArray, "light flare color-shift array");
for(unsigned int n = 0; n < node.Light.FlareColorShiftArray.nCount; n++){
node.Light.FlareColorShifts[n].fR = ReadNumber<float>(nullptr, 2, sLight, &nPosData);
node.Light.FlareColorShifts[n].fG = ReadNumber<float>(nullptr, 2, sLight, &nPosData);
node.Light.FlareColorShifts[n].fB = ReadNumber<float>(nullptr, 2, sLight, &nPosData);
MarkDataBorder(nPosData - 1);
}
}
}
if(node.Head.nType & NODE_EMITTER){
std::string sEmitter = sNode.substr(0, sNode.size() - 3) + " > Header > Emitter";
node.Emitter.fDeadSpace = ReadNumber<float>(nullptr, 2, sEmitter);
node.Emitter.fBlastRadius = ReadNumber<float>(nullptr, 2, sEmitter);
node.Emitter.fBlastLength = ReadNumber<float>(nullptr, 2, sEmitter);
ReadNumber(&node.Emitter.nBranchCount, 1, sEmitter);
node.Emitter.fControlPointSmoothing = ReadNumber<float>(nullptr, 2, sEmitter);
ReadNumber(&node.Emitter.nxGrid, 4, sEmitter);
ReadNumber(&node.Emitter.nyGrid, 4, sEmitter);
ReadNumber(&node.Emitter.nSpawnType, 4, sEmitter);
ReadString(&node.Emitter.cUpdate, 32, 3, sEmitter);
ReadString(&node.Emitter.cRender, 32, 3, sEmitter);
ReadString(&node.Emitter.cBlend, 32, 3, sEmitter);
ReadString(&node.Emitter.cTexture, 32, 3, sEmitter);
ReadString(&node.Emitter.cChunkName, 16, 3, sEmitter);
ReadNumber(&node.Emitter.nTwosidedTex, 4, sEmitter);
ReadNumber(&node.Emitter.nLoop, 4, sEmitter);
ReadNumber(&node.Emitter.nRenderOrder, 5, sEmitter);
ReadNumber(&node.Emitter.nFrameBlending, 7, sEmitter);
ReadString(&node.Emitter.cDepthTextureName, 32, 3, sEmitter);
ReadNumber(&node.Emitter.nPadding1, 11, sEmitter);
ReadNumber(&node.Emitter.nFlags, 4, sEmitter);
MarkDataBorder(nPosition - 1);
}
if(node.Head.nType & NODE_REFERENCE){
std::string sReference = sNode.substr(0, sNode.size() - 3) + " > Header > Reference";
ReadString(&node.Reference.sRefModel, 32, 3, sReference);
ReadNumber(&node.Reference.nReattachable, 4, sReference);
MarkDataBorder(nPosition - 1);
}
if(node.Head.nType & NODE_MESH){
std::string sMesh = sNode.substr(0, sNode.size() - 3) + " > Header > Mesh";
std::string sMeshData = sNode.substr(0, sNode.size() - 3);
try{
ReadNumber(&node.Mesh.nFunctionPointer0, 9, sMesh);
ReadNumber(&node.Mesh.nFunctionPointer1, 9, sMesh);
ReadNumber(&node.Mesh.FaceArray.nOffset, 6, sMesh);
ReadNumber(&node.Mesh.FaceArray.nCount, 1, sMesh);
ReadNumber(&node.Mesh.FaceArray.nCount2, 1, sMesh);
node.Mesh.vBBmin.fX = ReadNumber<float>(nullptr, 2, sMesh);
node.Mesh.vBBmin.fY = ReadNumber<float>(nullptr, 2, sMesh);
node.Mesh.vBBmin.fZ = ReadNumber<float>(nullptr, 2, sMesh);
node.Mesh.vBBmax.fX = ReadNumber<float>(nullptr, 2, sMesh);
node.Mesh.vBBmax.fY = ReadNumber<float>(nullptr, 2, sMesh);
node.Mesh.vBBmax.fZ = ReadNumber<float>(nullptr, 2, sMesh);
node.Mesh.fRadius = ReadNumber<float>(nullptr, 2, sMesh);
node.Mesh.vAverage.fX = ReadNumber<float>(nullptr, 2, sMesh);
node.Mesh.vAverage.fY = ReadNumber<float>(nullptr, 2, sMesh);
node.Mesh.vAverage.fZ = ReadNumber<float>(nullptr, 2, sMesh);
node.Mesh.fDiffuse.fR = ReadNumber<float>(nullptr, 2, sMesh);
node.Mesh.fDiffuse.fG = ReadNumber<float>(nullptr, 2, sMesh);
node.Mesh.fDiffuse.fB = ReadNumber<float>(nullptr, 2, sMesh);
node.Mesh.fAmbient.fR = ReadNumber<float>(nullptr, 2, sMesh);
node.Mesh.fAmbient.fG = ReadNumber<float>(nullptr, 2, sMesh);
node.Mesh.fAmbient.fB = ReadNumber<float>(nullptr, 2, sMesh);
ReadNumber(&node.Mesh.nTransparencyHint, 4, sMesh);
ReadString(&node.Mesh.cTexture1, 32, 3, sMesh);
ReadString(&node.Mesh.cTexture2, 32, 3, sMesh);
ReadString(&node.Mesh.cTexture3, 12, 3, sMesh);
ReadString(&node.Mesh.cTexture4, 12, 3, sMesh);
ReadNumber(&node.Mesh.IndexCounterArray.nOffset, 6, sMesh);
ReadNumber(&node.Mesh.IndexCounterArray.nCount, 1, sMesh);
ReadNumber(&node.Mesh.IndexCounterArray.nCount2, 1, sMesh);
ReadNumber(&node.Mesh.IndexLocationArray.nOffset, 6, sMesh);
ReadNumber(&node.Mesh.IndexLocationArray.nCount, 1, sMesh);
ReadNumber(&node.Mesh.IndexLocationArray.nCount2, 1, sMesh);
ReadNumber(&node.Mesh.MeshInvertedCounterArray.nOffset, 6, sMesh);
ReadNumber(&node.Mesh.MeshInvertedCounterArray.nCount, 1, sMesh);
ReadNumber(&node.Mesh.MeshInvertedCounterArray.nCount2, 1, sMesh);
ReadNumber(&node.Mesh.nUnknown3[0], 11, sMesh);
ReadNumber(&node.Mesh.nUnknown3[1], 8, sMesh);
ReadNumber(&node.Mesh.nUnknown3[2], 8, sMesh);
ReadNumber(&node.Mesh.nSaberUnknown1, 11, sMesh);
ReadNumber(&node.Mesh.nSaberUnknown2, 11, sMesh);
ReadNumber(&node.Mesh.nSaberUnknown3, 11, sMesh);
ReadNumber(&node.Mesh.nSaberUnknown4, 11, sMesh);
ReadNumber(&node.Mesh.nSaberUnknown5, 11, sMesh);
ReadNumber(&node.Mesh.nSaberUnknown6, 11, sMesh);
ReadNumber(&node.Mesh.nSaberUnknown7, 11, sMesh);
ReadNumber(&node.Mesh.nSaberUnknown8, 11, sMesh);
ReadNumber(&node.Mesh.nAnimateUV, 4, sMesh);
node.Mesh.fUVDirectionX = ReadNumber<float>(nullptr, 2, sMesh);
node.Mesh.fUVDirectionY = ReadNumber<float>(nullptr, 2, sMesh);
node.Mesh.fUVJitter = ReadNumber<float>(nullptr, 2, sMesh);
node.Mesh.fUVJitterSpeed = ReadNumber<float>(nullptr, 2, sMesh);
ReadNumber(&node.Mesh.nMdxDataSize, 1, sMesh);
ReadNumber(&node.Mesh.nMdxDataBitmap, 4, sMesh);
ReadNumber(node.Mesh.nOffsetToMdxVertex.GetPtr(), 6, sMesh);
ReadNumber(node.Mesh.nOffsetToMdxNormal.GetPtr(), 6, sMesh);
ReadNumber(node.Mesh.nOffsetToMdxColor.GetPtr(), 6, sMesh);
ReadNumber(node.Mesh.nOffsetToMdxUV1.GetPtr(), 6, sMesh);
ReadNumber(node.Mesh.nOffsetToMdxUV2.GetPtr(), 6, sMesh);
ReadNumber(node.Mesh.nOffsetToMdxUV3.GetPtr(), 6, sMesh);
ReadNumber(node.Mesh.nOffsetToMdxUV4.GetPtr(), 6, sMesh);
ReadNumber(node.Mesh.nOffsetToMdxTangent1.GetPtr(), 6, sMesh);
ReadNumber(node.Mesh.nOffsetToMdxTangent2.GetPtr(), 6, sMesh);
ReadNumber(node.Mesh.nOffsetToMdxTangent3.GetPtr(), 6, sMesh);
ReadNumber(node.Mesh.nOffsetToMdxTangent4.GetPtr(), 6, sMesh);
ReadNumber(&node.Mesh.nNumberOfVerts, 1, sMeshData + " > Data > Mesh > Pointer to Vert Number");
ReadNumber(&node.Mesh.nTextureNumber, 1, sMesh);
ReadNumber(&node.Mesh.nHasLightmap, 7, sMesh);
ReadNumber(&node.Mesh.nRotateTexture, 7, sMesh);
ReadNumber(&node.Mesh.nBackgroundGeometry, 7, sMesh);
ReadNumber(&node.Mesh.nShadow, 7, sMesh);
ReadNumber(&node.Mesh.nBeaming, 7, sMesh);
ReadNumber(&node.Mesh.nRender, 7, sMesh);
if(bK2) ReadNumber(&node.Mesh.nDirtEnabled, 7, sMesh);
if(bK2) ReadNumber(&node.Mesh.nPadding1, 11, sMesh);
if(bK2) ReadNumber(&node.Mesh.nDirtTexture, 5, sMesh);
if(bK2) ReadNumber(&node.Mesh.nDirtCoordSpace, 5, sMesh);
if(bK2) ReadNumber(&node.Mesh.nHideInHolograms, 7, sMesh);
if(bK2) ReadNumber(&node.Mesh.nPadding2, 11, sMesh);
ReadNumber(&node.Mesh.nPadding3, 11, sMesh);
node.Mesh.fTotalArea = ReadNumber<float>(nullptr, 2, sMesh);
ReadNumber(&node.Mesh.nPadding, 8, sMesh);
ReadNumber(&node.Mesh.nOffsetIntoMdx, 6, sMesh);
if(!bXbox) ReadNumber(&node.Mesh.nOffsetToVertArray, 6, sMesh);
MarkDataBorder(nPosition - 1);
}
catch(const std::exception & e){
throw mdlexception("In " + GetNodeName(node) + ", reading trimesh header: " + e.what());
}
if(node.Mesh.IndexCounterArray.nCount > 0){
//I am assuming here that the pointer can only ever be a single one
nPosData = MdlArrayDataOffset(node.Mesh.IndexCounterArray, "mesh index-counter array");
ReadNumber(&node.Mesh.nVertIndicesCount, 1, sMeshData + " > Data > Mesh > Inverted Counter", &nPosData);
MarkDataBorder(nPosData - 1);
}
else node.Mesh.nVertIndicesCount = 0;
if(node.Mesh.IndexLocationArray.nCount > 0){
//I am assuming here that the pointer can only ever be a single one