-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathGameData.cpp
More file actions
1143 lines (1038 loc) · 54 KB
/
Copy pathGameData.cpp
File metadata and controls
1143 lines (1038 loc) · 54 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
/* File: GameData.cpp
Project: Kanabo
Purpose: Loads a single game data file (level data file or instance file) into memory, used by GameDataManager to build a collection.
Notes: Calls on DiskItem to perform disk I/O on the game data files and then loads the contents of those files into objects of the classes
defined in Template.cpp. Some basic terminology for understanding the code: a level data file is one that contains a set of instances,
e.g. the .dat files in Oni's GameDataFolder. A raw and/or separate file combined with this level data file comprises a level data set;
the raw/separate files on their own are referred to as auxiliary data. An instance file is a file that contains a single resource,
such as OniSplit's .oni files. A template is a type of resource data, labeled with a tag such as TXMP. An instance is a single
resource of a template, such as TXMPKONdeepthought. Instance data is stored inside a level data file and sometimes additional data for
the instance is stored in the auxiliary data files as well. The PlayStation 2 port uses two additional types of aux data files:
palette files (.pal) and sound files (a set of .dat/.raw/.sep) created for each level.
Margin Guide: |----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----|
Copyright 2026 Iritscen */
#include "GameData.hpp"
#include <algorithm> // for ranges::any_of()
#include <array>
#include <cctype> // for isdigit
#include <format> // needed on Windows for std::format
#include <iomanip> // needed on Windows for std::quoted
#include <iostream> // for cout, cerr
using std::array;
using std::cerr;
using std::cout;
using std::format;
using std::function;
using std::lower_bound;
using std::make_unique;
using std::map;
using std::min;
using std::nullopt;
using std::optional;
using std::quoted;
using std::span;
using std::string;
using std::unique_ptr;
using std::unordered_multimap;
using std::vector;
/****************************** Globals ******************************/
// More file-reading constants (after the ones in the header)
map<uint32_t, uint32_t> kInstanceDescFlagValues = {{instFlagUnnamed, 0x000001}, // NOLINT(cert-err58-cpp)
{instFlagPlaceholder, 0x000002},
{instFlagDuplicate, 0x000004},
{instFlagDuplicateSource, 0x000008},
{instFlagUnsaved, 0x100000},
{instFlagInBatchFile, 0x200000},
{instFlagToDelete, 0x400000}};
map<uint32_t, string> kInstanceDescFlagNames = {{instFlagUnnamed, "unnamed"}, // NOLINT(cert-err58-cpp)
{instFlagPlaceholder, "placeholder"},
{instFlagDuplicate, "duplicate"},
{instFlagDuplicateSource, "duplicate source"},
{instFlagUnsaved, "unsaved"},
{instFlagInBatchFile, "in batch file"},
{instFlagToDelete, "to be deleted"}};
constexpr uint32_t kDescCountSafety = 100'000; // safety limit on descriptors per table; well above any real Oni level
constexpr uint32_t kDescSizeSafety = 64; // safety limit on descriptor size in bytes; largest known is WinA6's 32-byte instance descriptor
// Handy suffix groups for use with ::identifyFile(), etc.
namespace // enforce internal linkage
{
template<typename... Strings>
bool IsInSuffixGroup(const string & suffix, const Strings &... candidates)
{
return ((suffix == candidates) || ...);
}
bool InSuffixGroup_32Bit(const string & suffix)
{
return IsInSuffixGroup(suffix, kFileSuffix_LevelFile32, kFileSuffix_InstanceFile32, kFileSuffix_InstanceFile32_K, kFileSuffix_RawFile32,
kFileSuffix_SepFile32, kFileSuffix_PalFile);
}
bool InSuffixGroup_64Bit(const string & suffix)
{
return IsInSuffixGroup(suffix, kFileSuffix_LevelFile64, kFileSuffix_InstanceFile64, kFileSuffix_RawFile64, kFileSuffix_SepFile64);
}
bool InSuffixGroup_HasHeader(const string & suffix)
{
return IsInSuffixGroup(suffix, kFileSuffix_LevelFile32, kFileSuffix_InstanceFile32, kFileSuffix_InstanceFile32_K,
kFileSuffix_LevelFile64, kFileSuffix_InstanceFile64);
}
bool InSuffixGroup_Unstructured(const string & suffix)
{
return IsInSuffixGroup(suffix, kFileSuffix_RawFile32, kFileSuffix_SepFile32, kFileSuffix_PalFile);
}
bool InSuffixGroup_LevelFile(const string & suffix)
{
return IsInSuffixGroup(suffix, kFileSuffix_LevelFile32, kFileSuffix_LevelFile64);
}
bool InSuffixGroup_RawFile(const string & suffix)
{
return IsInSuffixGroup(suffix, kFileSuffix_RawFile32, kFileSuffix_RawFile64);
}
bool InSuffixGroup_SepFile(const string & suffix)
{
return IsInSuffixGroup(suffix, kFileSuffix_SepFile32, kFileSuffix_SepFile64);
}
bool InSuffixGroup_InstanceFile(const string & suffix)
{
return IsInSuffixGroup(suffix, kFileSuffix_InstanceFile32, kFileSuffix_InstanceFile32_K, kFileSuffix_InstanceFile64);
}
}
// A table of all template tags under the main namespace (as opposed to BINA and OBJC)
// Note: This table must be sorted numerically for binary lookup to work
constexpr int kUnsupported = 0;
static const InstanceFactoryEntry gInstanceFactoryTableMain[] =
{
{0x41424E41, nullptr}, // ABNA
{0x41474442, nullptr}, // AGDB
{0x41475143, nullptr}, // AGQC
{0x41475147, nullptr}, // AGQG
{0x41475152, nullptr}, // AGQR
{0x41495341, nullptr}, // AISA
{0x41495452, nullptr}, // AITR
{0x414B4141, nullptr}, // AKAA
{0x414B4241, nullptr}, // AKBA
{0x414B4250, nullptr}, // AKBP
{0x414B4441, nullptr}, // AKDA
{0x414B4556, nullptr}, // AKEV
{0x414B4F54, nullptr}, // AKOT
{0x414B5641, nullptr}, // AKVA
{0x42494E41, &TemplateBINA::create, {0xDB41, 0xDB41, 0xDB41, 0x15E11, 0x15E11}},
{0x43425049, &TemplateCBPI::create, {0xC0BF9D6C2, 0xC0BF9D6C2, 0xC0BF9D6C2, 0xC0BF9D6C2, 0xC0BF9D6C2}},
{0x4342504D, &TemplateCBPM::create, {0x26BA4351F, 0x26BA4351F, 0x26BA4351F, 0x26BA4351F, 0x26BA4351F}},
{0x434F4E53, nullptr}, // CONS
{0x43525341, nullptr}, // CRSA
{0x444F4F52, nullptr}, // DOOR
{0x44506765, nullptr}, // DPge
{0x454E5650, nullptr}, // ENVP
{0x45535549, nullptr}, // ESUI
{0x46494C4D, nullptr}, // FILM
{0x46584C46, nullptr}, // FXLF
{0x46585341, nullptr}, // FXSA
{0x48506765, nullptr}, // HPge
{0x49445841, &TemplateIDXA::create, {0x2708F, 0x2708F, 0x2708F, 0x2708F, 0x2708F}},
{0x49474848, nullptr}, // IGHH
{0x49475041, nullptr}, // IGPA
{0x49475047, nullptr}, // IGPG
{0x49475341, nullptr}, // IGSA
{0x49475374, nullptr}, // IGSt
{0x49506765, nullptr}, // IPge
{0x496D7074, nullptr}, // Impt
{0x4B657949, nullptr}, // KeyI
{0x4D334741, nullptr}, // M3GA
{0x4D33474D, &TemplateM3GM::create, {0x27A078E436, 0x27A078E436, 0x27A078E436, 0x27A078E436, 0x16C1058961}},
{0x4D74726C, nullptr}, // Mtrl
{0x4F42414E, nullptr}, // OBAN
{0x4F424443, nullptr}, // OBDC
{0x4F424F41, nullptr}, // OBOA
{0x4F464741, nullptr}, // OFGA
{0x4F4E4343, &TemplateONCC::create, {0x3647ABD0BF1, 0x4A5AAC759EF, 0x3647ABD0BF1, 0x4A5AAC759EF, 0x4A5AAC759EF}},
{0x4F4E4350, &TemplateONCP::create, {0x2157A8, 0x2F7321, 0x2157A8, 0x2F7321, 0x2F7321}},
{0x4F4E4356, &TemplateONCV::create, {0xC28B, 0x299F5, 0xC28B, 0x299F5, 0x299F5}},
{0x4F4E4641, nullptr}, // ONFA
{0x4F4E4753, nullptr}, // ONGS
{0x4F4E4941, &TemplateONIA::create, {0x2B2F9A, 0x2B2F9A, 0x2B2F9A, 0x2B2F9A, 0x2B2F9A}},
{0x4F4E4C44, nullptr}, // ONLD
{0x4F4E4C56, nullptr}, // ONLV
{0x4F4E4D41, nullptr}, // ONMA
{0x4F4E4F41, nullptr}, // ONOA
{0x4F4E5341, nullptr}, // ONSA
{0x4F4E534B, nullptr}, // ONSK
{0x4F4E5441, nullptr}, // ONTA
{0x4F4E564C, nullptr}, // ONVL
{0x4F4E5743, nullptr}, // ONWC
{0x4F506765, nullptr}, // OPge
{0x4F534244, nullptr}, // OSBD
{0x4F544954, nullptr}, // OTIT
{0x4F544C46, nullptr}, // OTLF
{0x504C4541, nullptr}, // PLEA
{0x504E5441, &TemplatePNTA::create, {0x37676C, 0x37676C, 0x37676C, 0x37676C, 0x37676C}},
{0x50535549, nullptr}, // PSUI
{0x5053704C, nullptr}, // PSpL
{0x50537063, nullptr}, // PSpc
{0x51544E41, nullptr}, // QTNA
{0x534E4444, &TemplateSNDD::create, {0, 0x370578, 0x411EB, 0x411EB, 0x411EB}}, // SNDD: absent in WinA6 (uses SND_ instead)
{0x534E445F, nullptr}, // SND_
{0x53554254, nullptr}, // SUBT
{0x53744E41, nullptr}, // StNA
{0x54524143, &TemplateTRAC::create, {0xF26E9FB2F, 0xF26E9FB2F, 0xF26E9FB2F, 0xF26E9FB2F, 0xF26E9FB2F}},
{0x5452414D, &TemplateTRAM::create, {0xF805FB2B4, 0x107E3CC918, 0x107E3CC918, 0x107E3CC918, kUnsupported}},
{0x54524153, &TemplateTRAS::create, {0x1FA21A930, 0x1FA21A930, 0x1FA21A930, 0x1FA21A930, 0x1FA21A930}},
{0x54524253, &TemplateTRBS::create, {0x2A2924239, 0x2A2924239, 0x2A2924239, 0x2A2924239, 0x2A2924239}},
{0x5452434D, &TemplateTRCM::create, {0x2392DE054E, 0x2392DE054E, 0x2392DE054E, 0x2392DE054E, 0x2392DE054E}},
{0x54524558, nullptr}, // TREX
{0x54524741, &TemplateTRGA::create, {0x5206B20F8, 0x5206B20F8, 0x5206B20F8, 0x5206B20F8, 0x5206B20F8}},
{0x54524745, nullptr}, // TRGE
{0x54524941, &TemplateTRIA::create, {0xAC482, 0xAC482, 0xAC482, 0xAC482, 0xAC482}},
{0x54524947, nullptr}, // TRIG
{0x54524D41, &TemplateTRMA::create, {0x599DE6D57, 0x599DE6D57, 0x599DE6D57, 0x599DE6D57, 0x599DE6D57}},
{0x54525343, &TemplateTRSC::create, {0x599786B17, 0x599786B17, 0x599786B17, 0x599786B17, 0x599786B17}},
{0x54525441, &TemplateTRTA::create, {0x759E8, 0x759E8, 0x759E8, 0x759E8, 0x759E8}},
{0x54534646, &TemplateTSFF::create, {0x59988945B, 0xA8A6C488A, 0x59988945B, 0xA8A6C488A, 0xA8A6C488A}},
{0x5453464C, &TemplateTSFL::create, {0x8DE29, 0x8DE29, 0x8DE29, 0x8DE29, 0x8DE29}},
{0x54534654, &TemplateTSFT::create, {0x12C69F9DB1, 0x16BA91DEEA, 0x16BA91DEEA, 0x16BA91DEEA, 0x16BA8D6017}},
{0x54534741, &TemplateTSGA::create, {0x2A4E98, 0x2A4E98, 0x2A4E98, 0x2A4E98, 0x22DCA8}},
{0x54537472, nullptr}, // TStr
{0x54555252, nullptr}, // TURR
{0x5458414E, &TemplateTXAN::create, {0xA8B134387, 0xA8B134387, 0xA8B134387, 0xA8B134387, 0xA8B134387}},
{0x54584341, &TemplateTXCA::create, {0x9141A, 0x9141A, 0x9141A, 0x9141A, 0x9141A}},
{0x54584D41, nullptr}, // TXMA
{0x54584D42, nullptr}, // TXMB
{0x54584D50, &TemplateTXMP::create, {0x891187581, 0x891187581, 0x8911EEB5F, 0x8911EEB5F, 0x98E2E2E01}},
{0x54787443, nullptr}, // TxtC
{0x56435241, &TemplateVCRA::create, {0x54739, 0x54739, 0x54739, 0x54739, 0x54739}},
{0x574D434C, nullptr}, // WMCL
{0x574D4444, nullptr}, // WMDD
{0x574D4D42, nullptr}, // WMMB
{0x574D4D5F, nullptr}, // WMM_
{0x57506765, nullptr} // WPge
};
static const InstanceFactoryEntry gInstanceFactoryTableBINA[] =
{
{0x4F424A43, &TemplateBINA_OBJC::create}, // used when an OBJC sub-tag's list is empty, because empty lists do not contain a sub-tag at all
{0x4F4E4945, nullptr}, // ONIE
{0x50415233, &TemplateBINA_PAR3::create},
{0x53414244, nullptr}, // SABD
{0x544D4244, nullptr} // TMBD
};
static const InstanceFactoryEntry gInstanceFactoryTableOBJC[] =
{
{0x43484152, &TemplateBINA_OBJC_CHAR::create},
{0x434D4254, &TemplateBINA_OBJC_CMBT::create},
{0x434F4E53, nullptr}, // CONS
{0x444F4F52, nullptr}, // DOOR
{0x464C4147, nullptr}, // FLAG
{0x4655524E, nullptr}, // FURN
{0x4D454C45, &TemplateBINA_OBJC_MELE::create},
{0x4E455554, nullptr}, // NEUT
{0x50415254, nullptr}, // PART
{0x50415452, nullptr}, // PATR
{0x50575255, nullptr}, // PWRU
{0x534E4447, nullptr}, // SNDG
{0x54524756, nullptr}, // TRGV
{0x54524947, nullptr}, // TRIG
{0x54555252, nullptr}, // TURR
{0x57454150, nullptr}, // WEAP
};
namespace // ditto previous namespace comment
{
const InstanceFactoryEntry * gInstanceFactoryTables[] = {gInstanceFactoryTableMain, gInstanceFactoryTableBINA,
gInstanceFactoryTableOBJC};
const string gInstanceFactoryTableNames[] = {"Main", "BINA", "BINA/OBJC"}; // NOLINT(cert-err58-cpp)
}
const size_t gInstanceFactoryTableSizes[] = {std::size(gInstanceFactoryTableMain), std::size(gInstanceFactoryTableBINA),
std::size(gInstanceFactoryTableOBJC)};
// Helper lambdas
namespace // ditto above
{
auto FormatHasSep = [](Format f) {return (f >= Format::MacBeta4);};
auto FormatUsesSepForBINA = [](Format f) {return (f >= Format::PC1_1);}; // BINA is found in separate file starting with v1.1, not beta 4
auto StrFromInt32 = [](uint32_t tag) {string s = format("{}{}{}{}", char(tag >> (8 * 3)), char(tag >> (8 * 2)), char(tag >> (8 * 1)),
char(tag >> (8 * 0))); return s;};
auto StrToInt32 = [](const string & s) -> int32_t {return (static_cast<int32_t>(static_cast<uint8_t>(s[0])) << 24) |
(static_cast<int32_t>(static_cast<uint8_t>(s[1])) << 16) |
(static_cast<int32_t>(static_cast<uint8_t>(s[2])) << 8) |
(static_cast<int32_t>(static_cast<uint8_t>(s[3])) << 0);};
}
/************************** Class Definition **************************/
// This constructor opens the specified file and acquires as much data as it can about the file, returning when it cannot proceed. Thus, a
// partially constructed GameData object is possible, which might still contain some useful information. The ::info() method can be called on
// such an object to learn what can be ascertained about the file.
GameData::GameData(const string & filePath, int stopAfter, optional<PS2FileContext> ps2Context)
: mainFile_(filePath, ReadingMode::Binary, ReportingMode::Full),
status_(LoadPhase::P0_None),
ps2Context_(std::move(ps2Context)),
type_(FileType::Unknown),
format_(Format::Unknown),
width_(BitWidth::Unknown),
creator_(Creator::Unknown),
rawFileSize_(0),
sepFileSize_(0),
totalChecksum_( "total checksum", 0x00),
packingVersion_( "packing version", 0x08),
headerSize_( "header size", 0x0C),
instDescSize_( "instance descriptor size", 0x0E),
tempDescSize_( "template descriptor size", 0x10),
nameDescSize_( "name descriptor size", 0x12),
instDescCount_( "instance descriptor count", 0x14),
nameDescCount_( "name descriptor count", 0x18),
tempDescCount_( "template descriptor count", 0x1C),
dataBlockOffset_( "data block offset", 0x20),
dataBlockSize_( "data block size", 0x24),
nameBlockOffset_( "name block offset", 0x28),
nameBlockSize_( "name block size", 0x2C),
auxBlockOffset_( "aux data block offset", 0x30, false),
auxBlockSize_( "aux data block size", 0x34, false),
maxDataVersion_( "highest data version", 0x38, false),
maxContentVersion_("highest content version", 0x3C, false)
{
if (mainFile_.opened())
status_ = LoadPhase::P1_Loaded;
else
return;
// If a PS2 context was provided, set the file's virtual name from it
if (ps2Context_.has_value() && not ps2Context_->virtualName.empty())
{
const string & vName = ps2Context_->virtualName;
size_t dotPos = vName.rfind('.');
mainFile_.stem() = (dotPos != string::npos) ? vName.substr(0, dotPos) : vName;
mainFile_.suffix() = (dotPos != string::npos) ? vName.substr(dotPos + 1) : "";
mainFile_.name() = vName;
cout << "PlayStation 2 name lookup performed: real file name is " << mainFile_.name() << ".\n";
}
vector<function<bool()>> pipeline =
{
[this]() {return readHeader();}, // read in the header data if this is a level data or instance file
[this]() {return identifyFile();}, // confirm that we recognize the type of game data file
[this]() {return loadInstanceDescriptors();}, // read in the instance descriptor table
[this]() {return loadNameDescriptors();}, // read in the name descriptor table
[this]() {return loadTemplateDescriptors();}, // read in the template descriptor table
[this]() {return loadAuxiliaryData();}, // load accompanying raw/separate files if this is a level data file
[this]() {return constructInstances();} // construct instances from data
};
// Perform requested tasks according to Kanabo's run mode
for (auto & task : pipeline)
{
if (status_ >= stopAfter || not task())
return;
status_++;
}
}
/************************** Private Methods **************************/
// If this is a file type with a standard header, attempt to read its fields; otherwise simply return "true"
bool GameData::readHeader()
{
if (InSuffixGroup_HasHeader(mainFile_.suffix()))
{
if (mainFile_.size() >= kFileHeaderSize)
{
headerFields_ = {&totalChecksum_, &packingVersion_, &headerSize_, &instDescSize_, &tempDescSize_, &nameDescSize_, &instDescCount_,
&nameDescCount_, &tempDescCount_, &dataBlockOffset_, &dataBlockSize_, &nameBlockOffset_, &nameBlockSize_,
&auxBlockOffset_, &auxBlockSize_, &maxDataVersion_, &maxContentVersion_};
// Read in each field
for (auto * field : headerFields_)
{
if (not DataIO::ReadData(field->var(), mainFile_))
return false;
}
}
else
cerr << "Advisory: File suffix indicates this is a level file or an instance file, but it is too small to contain a header.\n";
}
return true;
}
// Determine if we understand the format of this file, setting our "file information" private data members as we go. If it is not positively
// identified, it can still have the info argument run on it if it *seems* to be of a certain type that is not able to be definitively
// identified.
bool GameData::identifyFile(void)
{
const string & suffix = mainFile_.suffix();
// Set file type
if (InSuffixGroup_LevelFile(suffix))
type_ = FileType::LevelDataFile;
else if (InSuffixGroup_RawFile(suffix))
type_ = FileType::RawFile;
else if (InSuffixGroup_SepFile(suffix))
type_ = FileType::SepFile;
else if (suffix == kFileSuffix_PalFile)
type_ = FileType::PalFile;
else if (InSuffixGroup_InstanceFile(suffix))
{
type_ = FileType::InstanceFile;
auxBlockOffset_.show() = true;
auxBlockSize_.show() = true;
}
// else FileType::Unknown, already applied in GameData's constructor
// Set file format
if (totalChecksum_.val() == kTotalChecksum_WinA6)
format_ = Format::WinAlpha6;
else if (totalChecksum_.val() == kTotalChecksum_MacB4)
format_ = Format::MacBeta4;
else if (totalChecksum_.val() == kTotalChecksum_PC1_0)
format_ = Format::PC1_0;
else if (totalChecksum_.val() == kTotalChecksum_PC1_1)
format_ = Format::PC1_1;
else if (totalChecksum_.val() == kTotalChecksum_PS2)
format_ = Format::PS2;
// else Format::Unknown, already applied in GameData's constructor; format is also set below for 64-bit level data files because we need
// to look at packing version instead of checksum to identify those files
// Set bit width
if (InSuffixGroup_32Bit(suffix))
width_ = BitWidth::Width32;
else if (InSuffixGroup_64Bit(suffix))
width_ = BitWidth::Width64;
// else BitWidth::Unknown, already applied in GameData's constructor
// Set creator
if (suffix == kFileSuffix_LevelFile32 || suffix == kFileSuffix_InstanceFile32)
{
if (packingVersion_.str() == kPackingVersion_Bungie)
{
if (format_ == Format::PS2)
creator_ = Creator::Rockstar;
else
creator_ = Creator::Bungie;
}
if (packingVersion_.str() == kPackingVersion_OniSplit_1_0 || packingVersion_.str() == kPackingVersion_OniSplit_0_99)
creator_ = Creator::OniSplit;
}
else if (suffix == kFileSuffix_LevelFile64 || suffix == kFileSuffix_InstanceFile64)
{
packingVersion_.setByteOrder(ByteOrder::BE);
if (packingVersion_.str() == kPackingVersion_Kanabo)
{
creator_ = Creator::Kanabo;
maxDataVersion_.show() = true;
maxContentVersion_.show() = true;
}
}
else if (suffix == kFileSuffix_InstanceFile32_K)
{
// The .onix file is written to be byte-identical to the .oni file so there's no point in looking at the packing version
creator_ = Creator::Kanabo;
}
else if (suffix == kFileSuffix_RawFile64 || suffix == kFileSuffix_SepFile64)
{
FieldTag fileTag("file tag");
fileTag.setByteOrder(ByteOrder::BE);
DataIO::ReadData(fileTag.var(), mainFile_); // failure of this function is handled below because the creator will not be set
if ((suffix == kFileSuffix_RawFile64 && fileTag.str() == kFileTag_Raw) ||
(suffix == kFileSuffix_SepFile64 && fileTag.str() == kFileTag_Sep))
{
creator_ = Creator::Kanabo;
maxDataVersion_.show() = true;
maxContentVersion_.show() = true;
}
}
// else Creator::Unknown, already applied in GameData's constructor; this is the correct Creator for 32-bit .raw and .sep files
// We recognize this file if it was assigned enough identifiable properties above to clear this battery of tests; having a recognizable file
// suffix is not enough, except for 32-bit .raw, .sep and .pal files which cannot be positively identified because they are unstructured
// files and lack definitive identifiable characteristics
// Test 1: The file must have had its type set above based upon its suffix
bool identified = (type_ != FileType::Unknown);
// Test 2: The file must have had its format set based upon its header checksum and possibly its packing version
identified &= (InSuffixGroup_Unstructured(suffix) || format_ != Format::Unknown);
// Test 3: The file must have had its bit width determined (file suffix is considered enough of a basis for this)
identified &= (width_ != BitWidth::Unknown);
// Test 4: The file's creator must have been determined
identified &= (InSuffixGroup_Unstructured(suffix) || creator_ != Creator::Unknown);
// Test 5: The header must be the right size
identified &= (InSuffixGroup_Unstructured(suffix) || headerSize_.val() == kFileHeaderSize);
return identified;
}
// Read in the instance descriptors from the array that comes after the header; simply return "true" if this is not a file with instance
// descriptors
bool GameData::loadInstanceDescriptors()
{
if (type_ != FileType::InstanceFile && type_ != FileType::LevelDataFile)
return true; // nothing to do here
bool winAlpha6 = (totalChecksum_.val() == kTotalChecksum_WinA6);
// Set up all potential descriptor fields
FieldChecksum checksum("template checksum"); // Windows alpha 6 only
checksum.setOffset(kFileHeaderSize);
FieldTag tag("tag");
tag.setOffset(winAlpha6 ? checksum.offset() + sizeof(checksum.val()) : kFileHeaderSize);
FieldOffset dataOffset("data offset");
dataOffset.setOffset(tag.offset() + sizeof(tag.val()));
FieldOffset nameOffset("name offset");
nameOffset.setOffset(dataOffset.offset() + sizeof(dataOffset.val()));
FieldSize32 dataSize("data size");
dataSize.setOffset(nameOffset.offset() + sizeof(nameOffset.val()));
FieldFlagSet32 flags("descriptor flags");
flags.setOffset(dataSize.offset() + sizeof(dataSize.val()));
flags.setFlagValueMap(&kInstanceDescFlagValues);
flags.setFlagNameMap(&kInstanceDescFlagNames);
FieldDate32 date("creation date"); // Windows alpha 6 only
date.setOffset(flags.offset() + sizeof(flags.val()));
// Create iterable set of fields to read
vector<Field *> instanceDescFields = {&tag, &dataOffset, &nameOffset, &dataSize, &flags};
if (winAlpha6)
{
// Insert these fields in the correct places for the alpha 6 header
instanceDescFields.insert(instanceDescFields.begin(), &checksum);
instanceDescFields.push_back(&date);
}
// Reserve all memory in advance to avoid multiple reallocations
instanceDescs_.reserve(min(instDescCount_.val(), kDescCountSafety));
// Read in each instance descriptor
for (uint32_t i = 0; i < instDescCount_.val(); i++)
{
for (auto & field : instanceDescFields)
{
if (not DataIO::ReadData(field->var(), mainFile_, field->offset()))
return false;
}
instanceDescs_.emplace_back(checksum, tag, dataOffset, nameOffset, dataSize, flags, date);
// Advance all field offsets by one descriptor
for (auto & field : instanceDescFields)
field->offset() += instDescSize_.val();
}
return (not instanceDescs_.empty());
}
// Read in the name descriptors from the array that comes after the template descriptors; simply return "true" if this is not a file with name
// descriptors
bool GameData::loadNameDescriptors()
{
if (type_ != FileType::InstanceFile && type_ != FileType::LevelDataFile)
return true; // nothing to do here
FieldU32 index("index");
if (instDescSize_.val() > kDescSizeSafety || instDescCount_.val() > kDescCountSafety)
{
cerr << "Error: Instance descriptor table dimensions exceed safety limits.\n";
return false;
}
index.setOffset(kFileHeaderSize + (instDescSize_.val() * instDescCount_.val()));
FieldU32 garbage("runtime pointer");
garbage.setOffset(index.offset() + sizeof(index.val()));
nameDescs_.reserve(min(nameDescCount_.val(), kDescCountSafety));
for (uint32_t i = 0; i < nameDescCount_.val(); i++)
{
if (not DataIO::ReadData(index.var(), mainFile_, index.offset()))
return false;
if (not DataIO::ReadData(garbage.var(), mainFile_, garbage.offset()))
return false;
nameDescs_.emplace_back(index, garbage);
index.offset() += nameDescSize_.val();
}
return true;
}
// Read in the template descriptors from the array that comes after the name descriptors; simply return "true" if this is not a file with
// template descriptors
bool GameData::loadTemplateDescriptors()
{
if (type_ != FileType::InstanceFile && type_ != FileType::LevelDataFile)
return true; // nothing to do here
FieldChecksum checksum("template checksum");
if (instDescSize_.val() > kDescSizeSafety || instDescCount_.val() > kDescCountSafety
|| nameDescSize_.val() > kDescSizeSafety || nameDescCount_.val() > kDescCountSafety)
{
cerr << "Error: Descriptor table dimensions exceed safety limits.\n";
return false;
}
checksum.setOffset(kFileHeaderSize + (instDescSize_.val() * instDescCount_.val()) + (nameDescSize_.val() * nameDescCount_.val()));
FieldTag tag("tag");
tag.setOffset(checksum.offset() + sizeof(checksum.val()));
FieldU32 numUsed("num used");
numUsed.setOffset(tag.offset() + sizeof(tag.val()));
vector<Field *> templateDescFields = {&checksum, &tag, &numUsed};
templateDescs_.reserve(min(tempDescCount_.val(), kDescCountSafety));
for (uint32_t i = 0; i < tempDescCount_.val(); i++)
{
for (auto & field : templateDescFields)
{
if (not DataIO::ReadData(field->var(), mainFile_, field->offset()))
return false;
}
templateDescs_.emplace_back(checksum, tag, numUsed);
// Advance all field offsets by one descriptor
for (auto & field : templateDescFields)
field->offset() += tempDescSize_.val();
}
return true;
}
// Copy auxiliary data into a devoted memory space, whether it comes from a level data file's companion files or the aux data block of an
// instance file
bool GameData::loadAuxiliaryData()
{
constexpr string kFileBody_LevelFile = "Final";
constexpr string kFileBody_PalFile = "palette";
// If this is an instance file, just copy the aux data block
if (type_ == FileType::InstanceFile)
{
size_t auxOffset = auxBlockOffset_.val();
size_t auxSize = auxBlockSize_.val();
if (auxOffset + auxSize > mainFile_.contents().size())
{
cerr << "Error: Aux data block in instance file claims to extend beyond the end of the file.\n";
return false;
}
vector<char>::iterator auxStart = mainFile_.contents().begin() + static_cast<ptrdiff_t>(auxOffset);
oniFileData_ = vector<char>(auxStart, auxStart + static_cast<ptrdiff_t>(auxSize));
return true;
}
// Rest of this code is for handling level data files
if (type_ != FileType::LevelDataFile)
return true;
// Prepare a map with the suffixes of the auxiliary data file(s)
unordered_multimap<string, string> suffixLookups{};
if (width_ == BitWidth::Width32)
{
suffixLookups.emplace(kFileSuffix_LevelFile32, kFileSuffix_RawFile32);
if (FormatHasSep(format_))
suffixLookups.emplace(kFileSuffix_LevelFile32, kFileSuffix_SepFile32);
if (format_ == Format::PS2)
suffixLookups.emplace(kFileSuffix_LevelFile32, kFileSuffix_PalFile);
}
else
{
suffixLookups.emplace(kFileSuffix_LevelFile64, kFileSuffix_RawFile64);
if (FormatHasSep(format_))
suffixLookups.emplace(kFileSuffix_LevelFile64, kFileSuffix_SepFile64);
}
// Use path of level data file to find and open auxiliary files
for (auto & suffixLookup : suffixLookups)
{
string fullPath;
if (format_ < Format::PS2)
{
// Change instance file path to raw/separate file path using simple suffix replacement
fullPath = mainFile_.path();
size_t pos = fullPath.find(suffixLookup.first);
if (pos != string::npos)
fullPath.replace(pos, suffixLookup.first.size(), suffixLookup.second);
}
else // Format::PS2
{
// For PS2 we have to perform a special lookup because of the different directory structure and the renamed files
string stem = mainFile_.stem();
if (suffixLookup.second == kFileSuffix_PalFile)
{
size_t bodyPos = stem.find(kFileBody_LevelFile);
if (bodyPos != string::npos)
stem.replace(bodyPos, kFileBody_LevelFile.size(), kFileBody_PalFile);
}
string virtualFileName = stem + "." + suffixLookup.second;
if (not ps2Context_.has_value() || not ps2Context_->fileMap)
{
cerr << "Error: No PS2 file map is available to locate auxiliary file " << quoted(virtualFileName) << ".\n";
return false;
}
auto it = ps2Context_->fileMap->find(virtualFileName);
if (it == ps2Context_->fileMap->end())
{
cerr << "Error: PS2 file map does not contain an entry for auxiliary file " << quoted(virtualFileName) << ".\n";
return false;
}
fullPath = it->second;
}
// Open auxiliary file
DiskItem auxFile(fullPath, ReadingMode::Binary, ReportingMode::ErrorsOnly);
if (not auxFile.opened())
return false;
// Get a copy of the file data that will persist after the DiskItem is implicitly destroyed when we leave this scope; this copy of the
// data is also removed from memory at the end of ::constructInstances() after it has been used in the creation of instances
auto readOpt = auxFile.readAllCopy(); // put std::optional result of read attempt into something we can test
if (readOpt.has_value()) // move it over if we're all good
{
if (suffixLookup.second == kFileSuffix_RawFile32 || suffixLookup.second == kFileSuffix_RawFile64)
{
rawFileData_ = std::move(*readOpt);
rawFileSize_ = rawFileData_.size();
}
else if (suffixLookup.second == kFileSuffix_SepFile32 || suffixLookup.second == kFileSuffix_SepFile64)
{
sepFileData_ = std::move(*readOpt);
sepFileSize_ = sepFileData_.size();
}
else // kFileSuffix_PalFile
palFileData_ = std::move(*readOpt);
}
else
return false;
}
// PS2 sound sample data lives outside the GameDataFolder, in a separate set of SOUND.DAT/RAW/SEP files; load those too. This is best-effort:
// a level without resolvable sounds is reported but does not abort the load.
if (format_ == Format::PS2)
loadPS2SoundFiles();
return true;
}
// For a PS2 level file, load the level's SOUND.RAW and SOUND.SEP into memory and parse its SOUND.DAT into "soundTable_" so that each PS2 SNDD
// instance can later locate and copy out its sample data in TemplateSNDD::processSoundData(). The SOUND files live in <soundsDir>/LEVEL<N>/,
// where N is the level number taken from the file's virtual name (e.g. "level3_Final.dat" -> LEVEL3). Returns true even when the files are
// missing so that a sound-free or unfound level does not abort the overall load.
bool GameData::loadPS2SoundFiles()
{
if (not ps2Context_.has_value() || ps2Context_->soundsDir.empty())
{
cerr << "Error: No SOUNDS folder was located for this PlayStation 2 install, so sound data could not be loaded.\n";
return true;
}
// Extract the level number from the virtual name, which has the form "level<N>_Final.dat"
const string & virtualName = ps2Context_->virtualName;
size_t numStart = virtualName.find("level");
if (numStart == string::npos)
{
cerr << "Error: Could not find a level name in " << quoted(virtualName) << ", so its sound data could not be loaded.\n";
return true;
}
numStart += 5; // skip past "level"
size_t numEnd = numStart;
while (numEnd < virtualName.size() && isdigit(static_cast<unsigned char>(virtualName[numEnd])))
numEnd++;
if (numEnd == numStart)
{
cerr << "Error: Could not determine the level number in " << quoted(virtualName) << ", so its sound data could not be loaded.\n";
return true;
}
string levelNum = virtualName.substr(numStart, numEnd - numStart);
// Locate the level's sound folder and read its three files; SOUND.DAT is required to build the index, while SOUND.RAW/SEP hold the data
string levelDir = DiskItem::joinPaths(ps2Context_->soundsDir, "LEVEL" + levelNum);
DiskItem datFile(DiskItem::joinPaths(levelDir, "SOUND.DAT"), ReadingMode::Binary, ReportingMode::ErrorsOnly);
if (not datFile.opened())
{
cerr << "Error: Could not open SOUND.DAT in " << quoted(levelDir) << ", so this level's sound data could not be loaded.\n";
return true;
}
auto datOpt = datFile.readAllCopy();
if (not datOpt.has_value())
return true;
vector<char> datData = std::move(*datOpt);
DiskItem rawFile(DiskItem::joinPaths(levelDir, "SOUND.RAW"), ReadingMode::Binary, ReportingMode::ErrorsOnly);
if (rawFile.opened())
{
auto rawOpt = rawFile.readAllCopy();
if (rawOpt.has_value())
soundRawData_ = std::move(*rawOpt);
}
DiskItem sepFile(DiskItem::joinPaths(levelDir, "SOUND.SEP"), ReadingMode::Binary, ReportingMode::ErrorsOnly);
if (sepFile.opened())
{
auto sepOpt = sepFile.readAllCopy();
if (sepOpt.has_value())
soundSepData_ = std::move(*sepOpt);
}
// Parse SOUND.DAT: a run of 10-byte blocks terminated by the two-byte sentinel FE FF. Each block is a 2-byte sound index, a 4-byte data size
// (including the data's leading 32-byte pad header), and a 4-byte offset into SOUND.SEP. A zero SEP offset means the data is in SOUND.RAW
// instead — except for the genuine first SEP entry, whose offset is legitimately zero. RAW offsets are not stored, so we derive each one by
// accumulating the sizes of the RAW-resident sounds in the order they appear.
constexpr uint16_t kSoundDatSentinel = 0xFFFE; // the bytes FE FF read as a little-endian uint16
bool sawFirstSepZero = false;
uint32_t rawRunningOffset = 0;
size_t pos = 0;
while (pos + 10 <= datData.size())
{
uint16_t index = 0;
uint32_t size = 0, sepOffset = 0;
if (not DataIO::ReadNum(index, datData, pos))
break;
if (index == kSoundDatSentinel)
break;
DataIO::ReadNum(size, datData, pos + 2);
DataIO::ReadNum(sepOffset, datData, pos + 6);
PS2SoundEntry entry{};
entry.size = size;
if (sepOffset == 0 && sawFirstSepZero)
{
entry.inRaw = true;
entry.offset = rawRunningOffset;
rawRunningOffset += size;
}
else
{
entry.inRaw = false;
entry.offset = sepOffset;
if (sepOffset == 0)
sawFirstSepZero = true;
}
soundTable_[index] = entry;
pos += 10;
}
return true;
}
// Build our internal vector of Template objects, creating each one and passing it the instance data which belongs in it
bool GameData::constructInstances()
{
if (type_ != FileType::LevelDataFile && type_ != FileType::InstanceFile)
return true;
// Build map of supported template checksums
map<int32_t, uint64_t> templateChecksumMap;
for (auto & tdesc : templateDescs_)
templateChecksumMap[static_cast<int32_t>(tdesc.tdTag.val())] = tdesc.tdChecksum.val();
uint32_t instNum = 0;
for (auto & desc : instanceDescs_)
{
// Set tag to default; for BINA subtypes the block below will build a fully qualified tag string
string tagToSearch = StrFromInt32(desc.idTag.val());
// If this is a BINA subtype, peek into the aux data to find the sub-tag and build the qualified tag string
if (tagToSearch == "BINA")
{
// Set up a ref to the right aux file for convenience since we will be reading from the file up to three times
vector<char> & auxFile = (FormatUsesSepForBINA(format_) ? sepFileData_ : rawFileData_);
// To find the BINA sub-tag, we first need to peek at the BINA data, specifically the second field
FieldOffset auxDataOffset("BINA aux data offset");
auxDataOffset.offset() = dataBlockOffset_.val() + desc.idDataOffset.val() + 4; // second field in BINA, which is the aux data offset
DataIO::ReadData(auxDataOffset.var(), mainFile_.contents(), auxDataOffset.offset());
// Now we have the address of the BINA data in the aux file, which starts with the sub-tag, so just read the sub-tag
FieldU32 binaTag("BINA sub-tag");
binaTag.setOffset(auxDataOffset.val());
DataIO::ReadData(binaTag.var(), auxFile, binaTag.offset());
// If this is an OBJC subtype, we have to make sure the subtype's object list is not empty, because if it is then it won't even have
// a sub-tag in it
if (StrFromInt32(binaTag.val()) == "OBJC")
{
FieldU32 objcListSize("list size");
objcListSize.setOffset(binaTag.offset() + 0xC); // location of "next object size" field for first object in list
DataIO::ReadData(objcListSize.var(), auxFile, objcListSize.offset());
bool listEmpty = objcListSize.val() == 0;
// If the list is empty, fall back to OBJC itself, otherwise read the list's sub-tag and set our search tag to that
if (listEmpty)
tagToSearch = "BINA/OBJC";
else
{
// Read the sub-tag that falls under OBJC
FieldU32 objcTag("OBJC sub-tag");
objcTag.setOffset(binaTag.offset() + 0x10); // size of OBJC header, after which the sub-tag is found
DataIO::ReadData(objcTag.var(), auxFile, objcTag.offset());
tagToSearch = "BINA/OBJC/" + StrFromInt32(objcTag.val());
}
}
else // it's another BINA subtype
tagToSearch = "BINA/" + StrFromInt32(binaTag.val());
}
// Create instance with our factory; gather the template checksum for this instance's tag and send it to makeInstance() for validation
string instName = getInstanceName(instNum);
uint64_t fileChecksum = 0;
auto checksumIt = templateChecksumMap.find(static_cast<int32_t>(desc.idTag.val()));
if (checksumIt != templateChecksumMap.end())
fileChecksum = checksumIt->second;
unique_ptr<Template> instance = makeInstance(tagToSearch, instName, format_, fileChecksum);
if (instance == nullptr)
{
cerr << "Error: Factory failed to create a template instance for " << quoted(instName) << ", even with the catch-all template.\n";
return false;
}
// Save values of flags that we'll need in the future
instance->isPlaceholder_ = DataIO::TestFlag(&kInstanceDescFlagValues, desc.idFlags.val(), instFlagPlaceholder);
instance->isUnnamed_ = DataIO::TestFlag(&kInstanceDescFlagValues, desc.idFlags.val(), instFlagUnnamed);
// Create span on this instance's portion of the data block and pass it to the instance for processing. We back up from the start of the
// instance data to include the instance header which occurs before the data offset listed in the instance descriptor table.
if (not instance->isPlaceholder_)
{
vector<char>::iterator fileBegin = mainFile_.contents().begin();
// Next line is a silly safeguard to prevent warnings from analysis tools
size_t headerToSubtract = min(static_cast<size_t>(kInstHeaderSize), static_cast<size_t>(desc.idDataOffset.val()));
size_t dataOffset = static_cast<size_t>(dataBlockOffset_.val()) + desc.idDataOffset.val() - headerToSubtract;
uint32_t dataSize = desc.idDataSize.val();
if (dataOffset + dataSize > mainFile_.contents().size())
{
cerr << "Error: Instance " << quoted(instName) << " claims to have data that extends beyond the end of the file.\n";
return false;
}
span<char> dataSpan(fileBegin + static_cast<ptrdiff_t>(dataOffset), fileBegin + static_cast<ptrdiff_t>(dataOffset + dataSize));
if (not dataSpan.empty())
{
instance->processData(dataSpan);
// If this template contains any fields that use aux data, pass all of the data to them for copying what they need
if (instance->hasAuxData_)
{
if (type_ == FileType::InstanceFile)
instance->processAuxData(oniFileData_);
else // FileType::LevelDataFile
{
if (instance->hasSepData())
instance->processAuxData(sepFileData_);
else
instance->processAuxData(rawFileData_);
}
}
// If this template requires palette data (i.e. it's a PS2 TXMP), pass the palette file in order to create a span from it
if (instance->hasPaletteData_)
instance->processPaletteData(palFileData_);
// If this template's sample data lives in the PS2 SOUND files (i.e. it's a PS2 SNDD), pass the buffers and index table so it can
// locate and copy out its data
if (instance->hasSoundData_)
instance->processSoundData(soundRawData_, soundSepData_, soundTable_);
// If this template contains any fields with instance links, pass our lookup function to it for use in resolving the links
if (instance->hasLinks_)
instance->processLinks(std::bind(&GameData::getInstanceName, this, std::placeholders::_1));
}
else
{
cerr << "Error: Failed to create data span for instance.\n";
return false;
}
}
else
instance->index_.val() = instNum;
// Add instance to vector
instances_.push_back(std::move(instance));
instNum++;
}
// Give instances with cross-instance dependencies a chance to finalize their aux data now that all instances are constructed and linked.
for (auto & inst : instances_)
inst->finalizeAuxData(instances_);
// Deallocate file-level copies of the aux data from memory now that the constituent data's been copied as needed by each instance during
// the calls to instance->processAuxData() or instance->processSoundData() above. The palette file (palFileData_, which only has contents if
// this is PS2 game data) is NOT freed from memory because PS2 TXMP instances continue to point to it for their lifespan; each palette can
// be used by multiple textures, so rather than duplicate 1 KiB of palette data under each TXMP instance, each instance contains a span
// pointing to the segment of palFileData_ with its palette.
rawFileData_.clear();
rawFileData_.shrink_to_fit();
sepFileData_.clear();
sepFileData_.shrink_to_fit();
oniFileData_.clear();
oniFileData_.shrink_to_fit();
soundRawData_.clear();
soundRawData_.shrink_to_fit();
soundSepData_.clear();
soundSepData_.shrink_to_fit();
return true;
}
// Search the factory table hierarchy to find the template class for the given tag and return an instance of it.
// For subtypes ("BINA/OBJC/CONS", "BINA/TMBD", etc.), it starts in the most specific table and cascades back toward the
// main table on each miss, also walking back the search tag to the parent level (OBJC → BINA → main). For templates
// that are in the table but not yet implemented (nullptr factory), TemplateCatchAll is used. If the tag is not found
// anywhere in the applicable tables, the function also falls back to TemplateCatchAll.
unique_ptr<Template> GameData::makeInstance(const string & qualifiedTag, const string & instName, Format dataFormat, uint64_t fileChecksum)
{
// Determine the bare 4CC and build the ordered search sequence as (tableIndex, tag) pairs based on the qualified
// tag's prefix; on each miss, both the table and the search tag step back to the parent level so that the fallback
// produces the right parent template rather than TemplateCatchAll
struct SearchStep
{
int tableIdx;
int32_t searchTag;
};
array<SearchStep, 3> steps;
size_t numSteps;
string fourCC;
if (qualifiedTag.starts_with("BINA/OBJC/") && qualifiedTag.size() == 14)
{
fourCC = qualifiedTag.substr(10);
steps[0] = {2, StrToInt32(fourCC)}; // OBJC table, searching for the specific OBJC subtype
steps[1] = {1, StrToInt32("OBJC")}; // BINA table, searching for OBJC itself
steps[2] = {0, StrToInt32("BINA")}; // main table, searching for BINA itself
numSteps = 3;
}
else if (qualifiedTag.starts_with("BINA/") && qualifiedTag.size() == 9)
{
fourCC = qualifiedTag.substr(5);