/
fmt_RE_MESH.py
5353 lines (4696 loc) · 205 KB
/
fmt_RE_MESH.py
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
#RE Engine [PC] - ".mesh" plugin for Rich Whitehouse's Noesis
#Authors: alphaZomega, Gh0stblade
#Special thanks: Chrrox, SilverEzredes, Enaium
Version = "v3.23 (April 5, 2024)"
#Options: These are global options that change or enable/disable certain features
#Var Effect
#Export Extensions
bRayTracingExport = True #Enable or disable the export of RE2R, RE3R and RE7 RayTracing meshes and textures
bRE2Export = True #Enable or disable export of mesh.1808312334 and tex.10 from the export list
bRE3Export = True #Enable or disable export of mesh.1902042334 and tex.190820018 from the export list
bDMCExport = True #Enable or disable export of mesh.1808282334 and tex.11 from the export list
bRE7Export = True #Enable or disable export of mesh.32 and tex.8 from the export list
bREVExport = True #Enable or disable export of mesh.2102020001 from the export list (and tex.31)
bRE8Export = True #Enable or disable export of mesh.2101050001 from the export list (and tex.30)
bMHRiseExport = False #Enable or disable export of mesh.2008058288 from the export list (and tex.28)
bMHRiseSunbreakExport = True #Enable or disable export of mesh.2109148288 from the export list (and tex.28)
bSF6Export = True #Enable or disable export of mesh.230110883 from the export list (and tex.143230113)
bRE4Export = True #Enable or disable export of mesh.221108797 from the export list (and tex.143221013)
bExoExport = True #Enable or disable export of mesh.220907984 from the export list (and tex.40)
bApolloExport = True #Enable or disable export of mesh.230612127 from the export list (and tex.719230324)
bDD2Export = True #Enable or disable export of mesh.231011879 from the export list (and tex.760230703)
#Mesh Global
fDefaultMeshScale = 100.0 #Override mesh scale (default is 1.0)
bMaterialsEnabled = True #Load MDF Materials
bRenderAsPoints = False #Render mesh as points without triangles drawn (1 = on, 0 = off)
bImportAllLODs = False #Imports all LODGroups (as separate models)
#Vertex Components (Import)
bNORMsEnabled = True #Normals
bTANGsEnabled = True #Tangents
bUVsEnabled = True #UVs
bSkinningEnabled = True #Enable skin weights
bColorsEnabled = True #Enable Vertex Colors
bDebugNormals = False #Debug normals as RGBA
bDebugTangents = False #Debug tangents as RGBA
#Import Options
bPrintMDF = False #Prints debug info for MDF files
bDebugMESH = False #Prints debug info for MESH files
bPopupDebug = True #Pops up debug window on opening MESH with MDF
bPrintFileList = True #Prints a list of files used by the MDF
bColorize = False #Colors the materials of the model and lists which material is which color
bUseOldNamingScheme = False #Names submeshes by their material ID (like in the MaxScript) rather than by their order in the file
bRenameMeshesToFilenames = False #For use with Noesis Model Merger. Renames submeshes to have their filenames in the mesh names
bImportMaterialNames = True #Imports material name data for each mesh, appending it to the end of each Submesh's name
bShorterNames = True #Imports meshes named like "LOD_1_Main_1_Sub_1" instead of "LODGroup_1_MainMesh_1_SubMesh_1"
bImportMips = False #Imports texture mip maps as separate images
texOutputExt = ".tga" #File format used when linking FBX materials to images
doConvertMatsForBlender = False #Load alpha maps as reflection maps, metallic maps as specular maps and roughness maps as bump maps for use with modified Blender FBX importer
bNoImportMenu = False #Hide the import menu on loading a mesh
#Export Options
bNewExportMenu = False #Show a custom Noesis window on mesh export
bAlwaysRewrite = False #Always try to rewrite the meshfile on export
bAlwaysWriteBones = False #Always write new skeleton to mesh
bNormalizeWeights = False #Makes sure that the weights of every vertex add up to 1.0, giving the remainder to the bone with the least influence
bCalculateBoundingBoxes = True #Calculates the bounding box for each bone
BoundingBoxSize = 1.0 #With bCalculateBoundingBoxes False, change the size of the bounding boxes created for each rigged bone when exporting with -bones or -rewrite
bRigToCoreBones = False #Assign non-matching bones to the hips and spine, when exporting a mesh without -bones or -rewrite
bSetNumModels = True #Sets the header byte "NumModels" to defaults when exporting without -rewrite, preventing crashes
bForceRootBoneToBone0 = True #If the root bone is detected as the last bone in the bones list, this will move it to be the first bone in the list
#Import/Export:
bAddBoneNumbers = 2 #Adds bone numbers and colons before bone names to indicate if they are active. 0 = Off, 1 = On, 2 = Auto
bRotateBonesUpright = False #Rotates bones to be upright for editing and then back to normal for exporting
bReadGroupIds = True #Import/Export with the GroupID as the MainMesh number
#Plugin GUI
iListboxSize = 280 #The height of the list box in the plugin's import menu
from inc_noesis import *
from collections import namedtuple
import noewin
import math
import os
import re
import copy
import time
def registerNoesisTypes():
def addOptions(handle):
noesis.setTypeExportOptions(handle, "-noanims -notex")
noesis.addOption(handle, "-bones", "Write new skeleton on export", 0)
noesis.addOption(handle, "-rewrite", "Rewrite submeshes and materials structure", 0)
noesis.addOption(handle, "-flip", "Reverse handedness from DirectX to OpenGL", 0)
noesis.addOption(handle, "-bonenumbers", "Add bone numbers to imported bones", 0)
noesis.addOption(handle, "-meshfile", "Export using a given source mesh filename", noesis.OPTFLAG_WANTARG)
noesis.addOption(handle, "-b", "Run as a batch process", 0)
noesis.addOption(handle, "-adv", "Show Advanced Export Options window", 0)
noesis.addOption(handle, "-vfx", "Export as VFX mesh", 0)
return handle
handle = noesis.register("RE Engine MESH [PC]", ".1902042334;.1808312334;.1808282334;.2008058288;.2102020001;.2101050001;.2109108288;.2109148288;.220128762;.220301866;.220721329;.221108797;.220907984;.230110883;.230612127;.231011879;.NewMesh")
noesis.setHandlerTypeCheck(handle, meshCheckType)
noesis.setHandlerLoadModel(handle, meshLoadModel)
noesis.addOption(handle, "-noprompt", "Do not prompt for MDF file", 0)
noesis.setTypeSharedModelFlags(handle, (noesis.NMSHAREDFL_WANTGLOBALARRAY))
handle = noesis.register("RE Engine Texture [PC]", ".10;.190820018;.11;.8;.28;.stm;.30;.31;.34;.35;.36;.40;.143221013;.143230113;.719230324;.760230703")
noesis.setHandlerTypeCheck(handle, texCheckType)
noesis.setHandlerLoadRGBA(handle, texLoadDDS)
handle = noesis.register("RE Engine UVS [PC]", ".5;.7")
noesis.setHandlerTypeCheck(handle, UVSCheckType)
noesis.setHandlerLoadModel(handle, UVSLoadModel)
handle = noesis.register("RE Engine SCN [PC]", ".19;.20")
noesis.setHandlerTypeCheck(handle, SCNCheckType)
noesis.setHandlerLoadModel(handle, SCNLoadModel)
handle = noesis.register("RE Engine MOTLIST [PC]", ".60;.85;.99;.484;.486;.500;.524;.528;.643;.653;.663;.750;.751")
noesis.setHandlerTypeCheck(handle, motlistCheckType)
noesis.setHandlerLoadModel(handle, motlistLoadModel)
if bRE2Export:
handle = noesis.register("RE2 Remake Texture [PC]", ".10")
noesis.setHandlerWriteRGBA(handle, texWriteRGBA)
handle = noesis.register("RE2 MESH", (".1808312334"))
noesis.setHandlerTypeCheck(handle, meshCheckType)
noesis.setHandlerWriteModel(handle, meshWriteModel)
addOptions(handle)
if bRE3Export:
handle = noesis.register("RE3 Remake Texture [PC]", ".190820018")
noesis.setHandlerWriteRGBA(handle, texWriteRGBA)
handle = noesis.register("RE3 MESH", (".1902042334"))
noesis.setHandlerTypeCheck(handle, meshCheckType)
noesis.setHandlerWriteModel(handle, meshWriteModel)
addOptions(handle)
#fbxskel export is disabled
#handle = noesis.register("fbxskel", (".fbxskel.3"))
#noesis.setHandlerWriteModel(handle, skelWriteFbxskel)
#noesis.setTypeExportOptions(handle, "-noanims -notex")
if bDMCExport:
handle = noesis.register("Devil May Cry 5 Texture [PC]", ".11")
noesis.setHandlerTypeCheck(handle, texCheckType)
noesis.setHandlerWriteRGBA(handle, texWriteRGBA)
handle = noesis.register("DMC5 MESH", (".1808282334"))
noesis.setHandlerTypeCheck(handle, meshCheckType)
noesis.setHandlerWriteModel(handle, meshWriteModel)
addOptions(handle)
if bREVExport or bRE8Export:
handle = noesis.register("RE8 / ReVerse Texture [PC]", ".30")
noesis.setHandlerTypeCheck(handle, texCheckType)
noesis.setHandlerWriteRGBA(handle, texWriteRGBA);
if bREVExport:
handle = noesis.register("ReVerse MESH", (".2102020001"))
noesis.setHandlerTypeCheck(handle, meshCheckType)
noesis.setHandlerWriteModel(handle, meshWriteModel)
addOptions(handle)
if bRE8Export:
handle = noesis.register("RE8 MESH", (".2101050001"))
noesis.setHandlerTypeCheck(handle, meshCheckType)
noesis.setHandlerWriteModel(handle, meshWriteModel)
addOptions(handle)
if bMHRiseExport or bMHRiseSunbreakExport:
handle = noesis.register("MHRise Texture [PC]", ".28;.stm")
noesis.setHandlerTypeCheck(handle, texCheckType)
noesis.setHandlerWriteRGBA(handle, texWriteRGBA);
if bMHRiseExport:
handle = noesis.register("MHRise MESH", (".2008058288"))
noesis.setHandlerTypeCheck(handle, meshCheckType)
noesis.setHandlerWriteModel(handle, meshWriteModel)
addOptions(handle)
if bMHRiseSunbreakExport:
handle = noesis.register("MHRise Sunbreak MESH", (".2109148288"))
noesis.setHandlerTypeCheck(handle, meshCheckType)
noesis.setHandlerWriteModel(handle, meshWriteModel)
addOptions(handle)
if bRE7Export:
handle = noesis.register("Resident Evil 7 Texture [PC]", ".8")
noesis.setHandlerTypeCheck(handle, texCheckType)
noesis.setHandlerWriteRGBA(handle, texWriteRGBA)
#RE7 MESH support is disabled for this version
if bRayTracingExport:
handle = noesis.register("RE2,3 RayTracing Texture [PC]", ".34")
noesis.setHandlerTypeCheck(handle, texCheckType)
noesis.setHandlerWriteRGBA(handle, texWriteRGBA);
handle = noesis.register("RE7 RayTracing Texture [PC]", ".35")
noesis.setHandlerTypeCheck(handle, texCheckType)
noesis.setHandlerWriteRGBA(handle, texWriteRGBA);
handle = noesis.register("RE2+3 RayTracing MESH", (".2109108288"))
noesis.setHandlerTypeCheck(handle, meshCheckType)
noesis.setHandlerWriteModel(handle, meshWriteModel)
addOptions(handle)
handle = noesis.register("RE7 RayTracing MESH", (".220128762"))
noesis.setHandlerTypeCheck(handle, meshCheckType)
noesis.setHandlerWriteModel(handle, meshWriteModel)
addOptions(handle)
if bSF6Export:
handle = noesis.register("Street Fighter 6 Texture [PC]", ".143230113;")
noesis.setHandlerTypeCheck(handle, texCheckType)
noesis.setHandlerWriteRGBA(handle, texWriteRGBA);
handle = noesis.register("Street Fighter 6 Mesh", (".230110883"))
noesis.setHandlerTypeCheck(handle, meshCheckType)
noesis.setHandlerWriteModel(handle, meshWriteModel)
addOptions(handle)
if bRE4Export:
handle = noesis.register("RE4 Remake Texture [PC]", ".143221013")
noesis.setHandlerTypeCheck(handle, texCheckType)
noesis.setHandlerWriteRGBA(handle, texWriteRGBA);
handle = noesis.register("RE4 Remake Mesh", (".221108797"))
noesis.setHandlerTypeCheck(handle, meshCheckType)
noesis.setHandlerWriteModel(handle, meshWriteModel)
addOptions(handle)
if bExoExport:
handle = noesis.register("ExoPrimal Texture [PC]", ".40")
noesis.setHandlerTypeCheck(handle, texCheckType)
noesis.setHandlerWriteRGBA(handle, texWriteRGBA);
handle = noesis.register("ExoPrimal Mesh", (".220907984"))
noesis.setHandlerTypeCheck(handle, meshCheckType)
noesis.setHandlerWriteModel(handle, meshWriteModel)
addOptions(handle)
if bApolloExport:
handle = noesis.register("Apollo Justice: Ace Attorney Trilogy Texture [PC]", ".719230324")
noesis.setHandlerTypeCheck(handle, texCheckType)
noesis.setHandlerWriteRGBA(handle, texWriteRGBA);
handle = noesis.register("Apollo Justice: Ace Attorney Trilogy Mesh", (".230612127"))
noesis.setHandlerTypeCheck(handle, meshCheckType)
noesis.setHandlerWriteModel(handle, meshWriteModel)
addOptions(handle)
if bDD2Export:
handle = noesis.register("Dragon's Dogma 2 Texture [PC]", ".760230703")
noesis.setHandlerTypeCheck(handle, texCheckType)
noesis.setHandlerWriteRGBA(handle, texWriteRGBA);
handle = noesis.register("Dragon's Dogma 2 Mesh", (".231011879"))
noesis.setHandlerTypeCheck(handle, meshCheckType)
noesis.setHandlerWriteModel(handle, meshWriteModel)
addOptions(handle)
noesis.logPopup()
return 1
#Default global variables for internal use:
sGameName = "RE2"
sExportExtension = ".1808312334"
bWriteBones = False
bDoVFX = False
bReWrite = False
openOptionsDialog = None
w1 = 127
w2 = -128
formats = {
"RE7": { "modelExt": ".-1", "texExt": ".8", "mmtrExt": ".69", "nDir": "x64", "mdfExt": ".mdf2.6", "meshVersion": 0, "mdfVersion": 0, "mlistExt": ".60", "meshMagic":352921600 },
"RE2": { "modelExt": ".1808312334", "texExt": ".10", "mmtrExt": ".1808160001", "nDir": "x64", "mdfExt": ".mdf2.10", "meshVersion": 1, "mdfVersion": 1, "mlistExt": ".85", "meshMagic":386270720 },
"DMC5": { "modelExt": ".1808282334", "texExt": ".11", "mmtrExt": ".1808168797", "nDir": "x64", "mdfExt": ".mdf2.10", "meshVersion": 1, "mdfVersion": 1, "mlistExt": ".85", "meshMagic":386270720 },
"RE3": { "modelExt": ".1902042334", "texExt": ".190820018", "mmtrExt": ".1905100741", "nDir": "stm", "mdfExt": ".mdf2.13", "meshVersion": 1, "mdfVersion": 2, "mlistExt": ".99", "meshMagic":386270720 },
"RE8": { "modelExt": ".2101050001", "texExt": ".30", "mmtrExt": ".2102188797", "nDir": "stm", "mdfExt": ".mdf2.19", "meshVersion": 2, "mdfVersion": 3, "mlistExt": ".486", "meshMagic":2020091500 },
"MHRise": { "modelExt": ".2008058288", "texExt": ".28", "mmtrExt": ".2109301553", "nDir": "stm", "mdfExt": ".mdf2.19", "meshVersion": 2, "mdfVersion": 3, "mlistExt": ".484", "meshMagic":21091000 },
"MHRSunbreak": { "modelExt": ".2109148288", "texExt": ".28", "mmtrExt": ".220427553", "nDir": "stm", "mdfExt": ".mdf2.23", "meshVersion": 2, "mdfVersion": 3, "mlistExt": ".528", "meshMagic":21091000 },
"ReVerse": { "modelExt": ".2102020001", "texExt": ".31", "mmtrExt": ".2108110001", "nDir": "stm", "mdfExt": ".mdf2.20", "meshVersion": 2, "mdfVersion": 3, "mlistExt": ".500", "meshMagic":2020091500 },
"RERT": { "modelExt": ".2109108288", "texExt": ".34", "mmtrExt": ".2109101635", "nDir": "stm", "mdfExt": ".mdf2.21", "meshVersion": 2, "mdfVersion": 3, "mlistExt": ".524", "meshMagic":21041600 },
"RE7RT": { "modelExt": ".220128762", "texExt": ".35", "mmtrExt": ".2109101635", "nDir": "stm", "mdfExt": ".mdf2.21", "meshVersion": 2, "mdfVersion": 3, "mlistExt": ".524", "meshMagic":21041600 },
"SF6": { "modelExt": ".230110883", "texExt": ".143230113", "mmtrExt": ".221102761", "nDir": "stm", "mdfExt": ".mdf2.31", "meshVersion": 3, "mdfVersion": 4, "mlistExt": ".653", "meshMagic":220705151 },
"ExoPrimal": { "modelExt": ".220907984", "texExt": ".40", "mmtrExt": ".221007878", "nDir": "stm", "mdfExt": ".mdf2.31", "meshVersion": 3, "mdfVersion": 4, "mlistExt": ".643", "meshMagic":220705151 },
"RE4": { "modelExt": ".221108797", "texExt": ".143221013", "mmtrExt": ".221007879", "nDir": "stm", "mdfExt": ".mdf2.32", "meshVersion": 3, "mdfVersion": 4, "mlistExt": ".663", "meshMagic":220822879 },
"AJ_AAT": { "modelExt": ".230612127", "texExt": ".719230324", "mmtrExt": ".230815080", "nDir": "stm", "mdfExt": ".mdf2.37", "meshVersion": 3, "mdfVersion": 4, "mlistExt": ".750", "meshMagic":230406984 },
"DD2": { "modelExt": ".231011879", "texExt": ".760230703", "mmtrExt": ".230815080", "nDir": "stm", "mdfExt": ".mdf2.40", "meshVersion": 3, "mdfVersion": 4, "mlistExt": ".751", "meshMagic":230517984 },
}
extToFormat = { #incomplete, just testing
"10": {
"albm": [99,5],
"albmsc": [99,5],
"alba": [99,1],
"alb": [72,0],
"nrmr": [98,6],
"nrm": [98,1],
"iam": [99,8],
"atos": [71,4],
"msk1": [80,3],
},
"34": {
"albm": [99,5],
"albmsc": [99,5],
"alba": [99,1],
"alb": [72,0],
"nrmr": [98,6],
"nrm": [98,1],
"iam": [99,8],
"atos": [71,4],
"msk1": [80,3],
},
}
extToFormat["8"] = extToFormat["10"]
extToFormat["11"] = extToFormat["10"]
extToFormat["190820018"] = extToFormat["10"]
extToFormat["30"] = extToFormat["34"]
extToFormat["35"] = extToFormat["34"]
extToFormat["28"] = extToFormat["34"]
extToFormat["143221013"] = extToFormat["34"]
extToFormat["28.stm"] = extToFormat["28"]
tex_format_list = {
0: "UNKNOWN",
1: "R32G32B32A32_TYPELESS",
2: "R32G32B32A32_FLOAT",
3: "R32G32B32A32_UINT",
4: "R32G32B32A32_SINT",
5: "R32G32B32_TYPELESS",
6: "R32G32B32_FLOAT",
7: "R32G32B32_UINT",
8: "R32G32B32_SINT",
9: "R16G16B16A16_TYPELESS",
10: "R16G16B16A16_FLOAT",
11: "R16G16B16A16_UNORM",
12: "R16G16B16A16_UINT",
13: "R16G16B16A16_SNORM",
14: "R16G16B16A16_SINT",
15: "R32G32_TYPELESS",
16: "R32G32_FLOAT",
17: "R32G32_UINT",
18: "R32G32_SINT",
19: "R32G8X24_TYPELESS",
20: "D32_FLOAT_S8X24_UINT",
21: "R32_FLOAT_X8X24_TYPELESS",
22: "X32_TYPELESS_G8X24_UINT",
23: "R10G10B10A2_TYPELESS",
24: "R10G10B10A2_UNORM",
25: "R10G10B10A2_UINT",
26: "R11G11B10_FLOAT",
27: "R8G8B8A8_TYPELESS",
28: "R8G8B8A8_UNORM",
29: "R8G8B8A8_UNORM_SRGB",
30: "R8G8B8A8_UINT",
31: "R8G8B8A8_SNORM",
32: "R8G8B8A8_SINT",
33: "R16G16_TYPELESS",
34: "R16G16_FLOAT",
35: "R16G16_UNORM",
36: "R16G16_UINT",
37: "R16G16_SNORM",
38: "R16G16_SINT",
39: "R32_TYPELESS",
40: "D32_FLOAT",
41: "R32_FLOAT",
42: "R32_UINT",
43: "R32_SINT",
44: "R24G8_TYPELESS",
45: "D24_UNORM_S8_UINT",
46: "R24_UNORM_X8_TYPELESS",
47: "X24_TYPELESS_G8_UINT",
48: "R8G8_TYPELESS",
49: "R8G8_UNORM",
50: "R8G8_UINT",
51: "R8G8_SNORM",
52: "R8G8_SINT",
53: "R16_TYPELESS",
54: "R16_FLOAT",
55: "D16_UNORM",
56: "R16_UNORM",
57: "R16_UINT",
58: "R16_SNORM",
59: "R16_SINT",
60: "R8_TYPELESS",
61: "R8_UNORM",
62: "R8_UINT",
63: "R8_SNORM",
64: "R8_SINT",
65: "A8_UNORM",
66: "R1_UNORM",
67: "R9G9B9E5_SHAREDEXP",
68: "R8G8_B8G8_UNORM",
69: "G8R8_G8B8_UNORM",
70: "BC1_TYPELESS",
71: "BC1_UNORM",
72: "BC1_UNORM_SRGB",
73: "BC2_TYPELESS",
74: "BC2_UNORM",
75: "BC2_UNORM_SRGB",
76: "BC3_TYPELESS",
77: "BC3_UNORM",
78: "BC3_UNORM_SRGB",
79: "BC4_TYPELESS",
80: "BC4_UNORM",
81: "BC4_SNORM",
82: "BC5_TYPELESS",
83: "BC5_UNORM",
84: "BC5_SNORM",
85: "B5G6R5_UNORM",
86: "B5G5R5A1_UNORM",
87: "B8G8R8A8_UNORM",
88: "B8G8R8X8_UNORM",
89: "R10G10B10_XR_BIAS_A2_UNORM",
90: "B8G8R8A8_TYPELESS",
91: "B8G8R8A8_UNORM_SRGB",
92: "B8G8R8X8_TYPELESS",
93: "B8G8R8X8_UNORM_SRGB",
94: "BC6H_TYPELESS",
95: "BC6H_UF16",
96: "BC6H_SF16",
97: "BC7_TYPELESS",
98: "BC7_UNORM",
99: "BC7_UNORM_SRGB",
100: "AYUV",
101: "Y410",
102: "Y416",
103: "NV12",
104: "P010",
105: "P016",
106: "_420_OPAQUE",
107: "YUY2",
108: "Y210",
109: "Y216",
110: "NV11",
111: "AI44",
112: "IA44",
113: "P8",
114: "A8P8",
115: "B4G4R4A4_UNORM",
130: "P208",
131: "V208",
132: "V408",
0xffffffff: "FORCE_UINT"
}
def sort_human(List):
convert = lambda text: float(text) if text.isdigit() else text
return sorted(List, key=lambda mesh: [convert(c) for c in re.split('([-+]?[0-9]*\.?[0-9]*)', mesh.name)])
def meshCheckType(data):
bs = NoeBitStream(data)
magic = bs.readUInt()
if magic == 0x4853454D:
return 1
else:
print("Fatal Error: Unknown file magic: " + str(hex(magic) + " expected 'MESH'!"))
return 0
def texCheckType(data):
bs = NoeBitStream(data)
magic = bs.readUInt()
if magic == 0x00584554:
return 1
else:
print("Fatal Error: Unknown file magic: " + str(hex(magic) + " expected TEX!"))
return 0
def UVSCheckType(data):
bs = NoeBitStream(data)
magic = bs.readUInt()
if magic == 1431720750:
return 1
else:
print("Fatal Error: Unknown file magic: " + str(hex(magic) + " expected ' SVU'!"))
return 0
def readUIntAt(bs, readAt):
pos = bs.tell()
bs.seek(readAt)
value = bs.readUInt()
bs.seek(pos)
return value
def readUShortAt(bs, tell):
pos = bs.tell()
bs.seek(tell)
out = bs.readUShort()
bs.seek(pos)
return out
def readUByteAt(bs, tell):
pos = bs.tell()
bs.seek(tell)
out = bs.readUByte()
bs.seek(pos)
return out
def ReadUnicodeString(bs):
numZeroes = 0
resultString = ""
while(numZeroes < 2):
c = bs.readUByte()
if c == 0:
numZeroes+=1
continue
else:
numZeroes = 0
resultString += chr(c)
return resultString
def readUnicodeStringAt(bs, tell):
string = []
pos = bs.tell()
bs.seek(tell)
while(readUShortAt(bs, bs.tell()) != 0):
string.append(bs.readByte())
bs.seek(1,1)
bs.seek(pos)
buff = struct.pack("<" + 'b'*len(string), *string)
return str(buff, 'utf-8')
def GetRootGameDir(path=""):
path = rapi.getDirForFilePath(path or rapi.getInputName())
while len(path) > 3:
lastFolderName = os.path.basename(os.path.normpath(path)).lower()
if lastFolderName == "stm" or lastFolderName == "x64":
break
else:
path = os.path.normpath(os.path.join(path, ".."))
return path + "\\"
def LoadExtractedDir(gameName=None):
gameName = gameName or sGameName
nativesPath = ""
try:
with open(noesis.getPluginsPath() + '\python\\' + gameName + 'NativesPath.txt') as fin:
nativesPath = fin.read()
fin.close()
except IOError:
pass
if not os.path.isdir(nativesPath):
return ""
return nativesPath
def SaveExtractedDir(dirIn, gameName=None):
gameName = gameName or sGameName
try:
print (noesis.getPluginsPath() + 'python\\' + gameName + 'NativesPath.txt')
with open(noesis.getPluginsPath() + 'python\\' + gameName + 'NativesPath.txt', 'w') as fout:
print ("Writing string: " + dirIn + " to " + noesis.getPluginsPath() + 'python\\' + gameName + 'NativesPath.txt')
fout.flush()
fout.write(str(dirIn))
fout.close()
except IOError:
print ("Failed to save natives path: IO Error")
return 0
return 1
def findRootDir(path):
idx = path.find("\\natives\\")
if idx != -1:
return path[:(idx + 9)]
return path
def getGlobalMatrix(noebone, bonesList): #doesnt work 100%?
mat = noebone.getMatrix()
parent = bonesList[noebone.parentIndex] if noebone.parentIndex != -1 else None
if parent:
mat *= parent.getMatrix().inverse()
return mat.transpose()
def getChildBones(parentBone, boneList, doRecurse=False):
children = []
for bone in boneList:
if bone.parentName == parentBone.name and bone not in children:
children.append(bone)
if doRecurse:
children.extend(getChildBones(bone, boneList, True))
break
return children
def cleanBoneName(name):
splitted = name.split(":", 1)
return splitted[len(splitted)-1]
def generateBoneMap(mdl):
usedBones = [False for bone in mdl.bones]
boneNames = [bone.name.lower() for bone in mdl.bones]
boneMapCount = 0
for bone in mdl.bones:
if bone.parentIndex != -1 and bone.parentName:
bone.parentIndex = boneNames.index(bone.parentName.lower())
for mesh in mdl.meshes:
for weightList in mesh.weights:
for idx in weightList.indices:
usedBones[idx] = True
for i, bone in enumerate(mdl.bones):
bone.name = cleanBoneName(bone.name)
if usedBones[i]:
bone.name = "b" + "{:03d}".format(boneMapCount) + ":" + bone.name
boneMapCount += 1
for bone in mdl.bones:
if bone.parentIndex != -1:
bone.parentName = mdl.bones[bone.parentIndex].name
def collapseBones(mdl, threshold=0.01):
print("Collapsing skeleton")
newBones = []
newBoneMap = []
newBoneMapNames = []
allBoneNames = [cleanBoneName(bone.name).lower() for bone in mdl.bones]
for i, bone in enumerate(mdl.bones):
boneMapId = i
name = allBoneNames[i].split(".", 1)[0]
sameBoneIdx = allBoneNames.index(name)
#if sameBoneIdx != i and (getGlobalMatrix(mdl.bones[sameBoneIdx], mdl.bones)[3] - getGlobalMatrix(bone, mdl.bones)[3]).length() < threshold * fDefaultMeshScale:
if sameBoneIdx != i: # and (mdl.bones[sameBoneIdx].getMatrix()[3] - bone.getMatrix()[3]).length() < threshold * fDefaultMeshScale:
boneMapId = sameBoneIdx
elif bone.parentIndex != bone.index:
newBones.append(bone)
newBoneMapNames.append(mdl.bones[boneMapId].name.lower())
for i, bone in enumerate(newBones):
bone.index = i
if bone.parentIndex != -1:
mat = bone.getMatrix() * mdl.bones[bone.parentIndex].getMatrix().inverse()
bone.parentName = newBoneMapNames[bone.parentIndex]
bone.setMatrix(mat * mdl.bones[allBoneNames.index(bone.parentName)].getMatrix()) #relocate bone
elif i > 0:
bone.parentName = newBones[0].name
newBoneNames = [bone.name.lower() for bone in newBones]
for i, boneName in enumerate(newBoneMapNames):
newBoneMap.append(newBoneNames.index(boneName))
for mesh in mdl.meshes:
for weightList in mesh.weights:
weightList.indices = list(weightList.indices)
for i, idx in enumerate(weightList.indices):
weightList.indices[i] = newBoneMap[idx]
'''for anim in mdl.anims:
anim.bones = newBones
for kfBone in anim.kfBones:
kfBone.boneIndex = newBoneMap[kfBone.boneIndex]'''
mdl.setBones(newBones)
def recombineNoesisMeshes(mdl):
meshesBySourceName = {}
for mesh in mdl.meshes:
meshesBySourceName[mesh.sourceName] = meshesBySourceName.get(mesh.sourceName) or []
meshesBySourceName[mesh.sourceName].append(mesh)
combinedMeshes = []
for sourceName, meshList in meshesBySourceName.items():
newPositions = []
newUV1 = []
newUV2 = []
newUV3 = []
newTangents = []
newWeights = []
newIndices = []
newColors = []
for mesh in meshList:
tempIndices = []
for index in mesh.indices:
tempIndices.append(index + len(newPositions))
newPositions.extend(mesh.positions)
newUV1.extend(mesh.uvs)
newUV2.extend(mesh.lmUVs)
newUV3.extend(mesh.uvxList[0] if len(mesh.uvxList) > 0 else [])
newColors.extend(mesh.colors)
newTangents.extend(mesh.tangents)
newWeights.extend(mesh.weights)
newIndices.extend(tempIndices)
combinedMesh = NoeMesh(newIndices, newPositions, meshList[0].sourceName, meshList[0].sourceName, mdl.globalVtx, mdl.globalIdx)
combinedMesh.setTangents(newTangents)
combinedMesh.setWeights(newWeights)
combinedMesh.setUVs(newUV1)
combinedMesh.setUVs(newUV2, 1)
combinedMesh.setUVs(newUV3, 2)
combinedMesh.setColors(newColors)
if len(combinedMesh.positions) > 65535:
print("Warning: Mesh exceeds the maximum of 65535 vertices (has", str(len(combinedMesh.positions)) + "):\n ", combinedMesh.name)
else:
combinedMeshes.append(combinedMesh)
return combinedMeshes
#murmur3 hash algorithm, credit to Darkness for adapting this:
def hash(key, getUnsigned=False):
seed = 0xffffffff
key = bytearray(key, 'utf8')
def fmix(h):
h ^= h >> 16
h = (h * 0x85ebca6b) & 0xFFFFFFFF
h ^= h >> 13
h = (h * 0xc2b2ae35) & 0xFFFFFFFF
h ^= h >> 16
return h
length = len(key)
nblocks = int(length / 4)
h1 = seed
c1 = 0xcc9e2d51
c2 = 0x1b873593
for block_start in range(0, nblocks * 4, 4):
k1 = key[block_start + 3] << 24 | \
key[block_start + 2] << 16 | \
key[block_start + 1] << 8 | \
key[block_start + 0]
k1 = (c1 * k1) & 0xFFFFFFFF
k1 = (k1 << 15 | k1 >> 17) & 0xFFFFFFFF
k1 = (c2 * k1) & 0xFFFFFFFF
h1 ^= k1
h1 = (h1 << 13 | h1 >> 19) & 0xFFFFFFFF
h1 = (h1 * 5 + 0xe6546b64) & 0xFFFFFFFF
tail_index = nblocks * 4
k1 = 0
tail_size = length & 3
if tail_size >= 3:
k1 ^= key[tail_index + 2] << 16
if tail_size >= 2:
k1 ^= key[tail_index + 1] << 8
if tail_size >= 1:
k1 ^= key[tail_index + 0]
if tail_size > 0:
k1 = (k1 * c1) & 0xFFFFFFFF
k1 = (k1 << 15 | k1 >> 17) & 0xFFFFFFFF
k1 = (k1 * c2) & 0xFFFFFFFF
h1 ^= k1
unsigned_val = fmix(h1 ^ length)
if getUnsigned or unsigned_val & 0x80000000 == 0:
return unsigned_val
else:
return -((unsigned_val ^ 0xFFFFFFFF) + 1)
def hash_wide(key, getUnsigned=False):
key_temp = ''
for char in key:
key_temp += char + '\x00'
return hash(key_temp, getUnsigned)
def forceFindTexture(FileName, startExtension=""):
global sGameName
for gameName, table in formats.items():
sGameName = gameName
ext = table["texExt"]
texFile = LoadExtractedDir() + FileName + ext
if rapi.checkFileExists(texFile):
return texFile, ext
return 0, 0
def readTextureData(texData, mipWidth, mipHeight, format):
fmtName = tex_format_list[format] if format in tex_format_list else ""
if format == 71 or format == 72: #ATOS
texData = rapi.imageDecodeDXT(texData, mipWidth, mipHeight, noesis.FOURCC_DXT1)
elif format == 77 or format == 78 or fmtName.find("BC3") != -1: #BC3
texData = rapi.imageDecodeDXT(texData, mipWidth, mipHeight, noesis.FOURCC_BC3)
elif format == 80 or fmtName.find("BC4") != -1: #BC4 wetmasks
texData = rapi.imageDecodeDXT(texData, mipWidth, mipHeight, noesis.FOURCC_BC4)
elif format == 83 or fmtName.find("BC5") != -1: #BC5
texData = rapi.imageDecodeDXT(texData, mipWidth, mipHeight, noesis.FOURCC_BC5)
texData = rapi.imageEncodeRaw(texData, mipWidth, mipHeight, "r16g16")
texData = rapi.imageDecodeRaw(texData, mipWidth, mipHeight, "r16g16")
elif format == 95 or format == 96 or fmtName.find("BC6") != -1:
texData = rapi.imageDecodeDXT(texData, mipWidth, mipHeight, noesis.FOURCC_BC6H)
elif format == 98 or format == 99 or fmtName.find("BC7") != -1:
texData = rapi.imageDecodeDXT(texData, mipWidth, mipHeight, noesis.FOURCC_BC7)
elif re.search("[RB]\d\d?", fmtName):
fmtName = fmtName.split("_")[0].lower()
texData = rapi.imageDecodeRaw(texData, mipWidth, mipHeight, fmtName)
else:
print("Fatal Error: Unsupported texture type: " + str(format))
return 0
#print("Detected texture format:", fmtName)
return texData, fmtName
def isImageBlank(imgData, width=None, height=None, threshold=1):
first = imgData[0]
if width and height and width * height > 4096:
imgData = rapi.imageResample(imgData, width, height, 64, 64)
for i, b in enumerate(imgData):
if (i+1) % 4 != 0 and abs(b - first) > threshold: #skip alpha
return False
return True
def invertRawRGBAChannel(imgData, channelID, bpp=4):
for i in range(int(len(imgData)/4)):
imgData[i*4+channelID] = 255 - imgData[i*4+channelID]
return imgData
def moveChannelsRGBA(sourceBytes, sourceChannel, sourceWidth, sourceHeight, targetBytes, targetChannels, targetWidth, targetHeight):
outputTargetBytes = copy.copy(targetBytes)
if sourceBytes == targetBytes and sourceChannel >= 0:
for ch in targetChannels:
outputTargetBytes = rapi.imageCopyChannelRGBA32(outputTargetBytes, sourceChannel, ch)
else:
resizedSourceBytes = rapi.imageResample(sourceBytes, sourceWidth, sourceHeight, targetWidth, targetHeight)
nullValue = 1 if sourceChannel == -1 else 255 if sourceChannel == -2 else None
for i in range(int(len(resizedSourceBytes)/16)):
for b in range(4):
for ch in targetChannels:
outputTargetBytes[i*16 + b*4 + ch] = nullValue or resizedSourceBytes[i*16 + b*4 + sourceChannel]
return outputTargetBytes
def generateDummyTexture4px(rgbaColor, name="Dummy"):
imageByteList = []
for i in range(16):
imageByteList.extend(rgbaColor)
imageData = struct.pack("<" + 'B'*len(imageByteList), *imageByteList)
imageData = rapi.imageDecodeRaw(imageData, 4, 4, "r8g8b8a8")
return NoeTexture(name, 4, 4, imageData, noesis.NOESISTEX_RGBA32)
def texLoadDDS(data, texList, texName=""):
texName = texName or rapi.getInputName()
bs = NoeBitStream(data)
magic = bs.readUInt()
version = bs.readUInt()
width = bs.readUShort()
height = bs.readUShort()
unk00 = bs.readUShort()
if version == 190820018:
version = 10
if version == 143221013:
version = 36
if version > 27:
numImages = bs.readUByte()
oneImgMipHdrSize = bs.readUByte()
mipCount = int(oneImgMipHdrSize / 16)
else:
mipCount = bs.readUByte()
numImages = bs.readUByte()
format = bs.readUInt()
unk02 = bs.readUInt()
unk03 = bs.readUInt()
unk04 = bs.readUInt()
if version > 27:
bs.seek(8,1)
mipData = []
for i in range(numImages):
mipDataImg = []
for j in range(mipCount):
mipDataImg.append([bs.readUInt64(), bs.readUInt(), bs.readUInt()]) #[0]offset, [1]pitch, [2]size
mipData.append(mipDataImg)
#bs.seek((mipCount-1)*16, 1) #skip small mipmaps
texFormat = noesis.NOESISTEX_RGBA32
tex = False
for i in range(numImages):
mipWidth = width
mipHeight = height
for j in range(mipCount):
try:
bs.seek(mipData[i][j][0])
texData = bs.readBytes(mipData[i][j][2])
except:
if i > 0:
numImages = i - 1
print ("Multi-image load stopped early")
break
else:
return 0
try:
texData, fmtName = readTextureData(texData, mipWidth, mipHeight, format)
except:
print("Failed", mipWidth, mipHeight, format, texData)
texData, fmtName = readTextureData(texData, mipWidth, mipHeight, format)
if texData == 0:
return 0
tex = NoeTexture(texName, int(mipWidth), int(mipHeight), texData, texFormat)
texList.append(tex)
if not bImportMips:
break
if mipWidth > 4:
mipWidth = int(mipWidth / 2)
if mipHeight > 4:
mipHeight = int(mipHeight / 2)
return tex
def getNoesisDDSType(imgType):
ddsFmt = noesis.NOE_ENCODEDXT_BC7
if imgType == 71 or imgType == 72: ddsFmt = noesis.NOE_ENCODEDXT_BC1
elif imgType == 80: ddsFmt = noesis.NOE_ENCODEDXT_BC4
elif imgType == 83: ddsFmt = noesis.NOE_ENCODEDXT_BC5
elif imgType == 95: ddsFmt = noesis.NOE_ENCODEDXT_BC6H
elif imgType == 98 or imgType == 99: ddsFmt = noesis.NOE_ENCODEDXT_BC7
elif imgType == 28 or imgType == 29: ddsFmt = "r8g8b8a8"
elif imgType == 77: ddsFmt = noesis.NOE_ENCODEDXT_BC3;
elif imgType == 10 or imgType == 95: ddsFmt = "r16g16b16a16"
elif imgType == 61: ddsFmt = "r8"
return ddsFmt
def findSourceTexFile(version_no, outputName=None):
newTexName = outputName or rapi.getOutputName().lower()
while newTexName.find("out.") != -1:
newTexName = newTexName.replace("out.",".")
newTexName = newTexName.replace(".dds","").replace(".tex","").replace(".jpg","").replace(".png","").replace(".tga","").replace(".gif","")
for gameName, tbl in formats.items():
newTexName = newTexName.replace(tbl["texExt"], "")
ext = ".tex." + str(version_no)
if not rapi.checkFileExists(newTexName + ext):
for other_ext, subDict in extToFormat.items():
if rapi.checkFileExists(newTexName + ".tex." + other_ext):
ext = ".tex." + other_ext
return newTexName + ext, ext
def convertTexVersion(version_no): #because RE3R and RE4R randomly decide to use timestamps for version numbers, which doesnt work well with using the others as versions
if version_no == 143221013:
return 36
if version_no == 190820018:
return 10
return version_no
def texWriteRGBA2(data, width, height, bs, version_no):
sourceFile = findSourceTexFile(10)
#print(str(sourceFile))
tex = texFile(data, width, height, sourceFile[0], rapi.getOutputName())
if not hasattr(tex, "error"):
bs = tex.writeTexHeader(bs)
tex.writeTexImageData(bs, 1)
return 1
else:
sourceFile = (sourceFile and sourceFile[0]) or rapi.getOutputName()
print("No format detected for " + sourceFile)
return 0
def texWriteRGBA(data, width, height, bs):
print ("\n ----RE Engine TEX Export----\n")
version_no = int(os.path.splitext(rapi.getOutputName())[1][1:])
#if noesis.optWasInvoked("-b"): # and version_no >= 28 and version_no < 1000: #batch / no-prompt
# return texWriteRGBA2(data, width, height, bs, version_no)
def getExportName(fileName):
if fileName == None:
newTexName = rapi.getOutputName().lower()
else:
newTexName = fileName
nonlocal version_no
guessedName, ext = findSourceTexFile(version_no)
newTexName = noesis.userPrompt(noesis.NOEUSERVAL_FILEPATH, "Export over tex", "Choose a tex file to export over", guessedName, None)
if newTexName == None:
print("Aborting...!")
return
return newTexName
fileName = None
if noesis.optWasInvoked("-b"):
newTexName, ext = findSourceTexFile(version_no)
else:
newTexName = getExportName(fileName)
if newTexName == None:
return 0
while not (rapi.checkFileExists(newTexName)):
print ("File not found")
newTexName = getExportName(fileName)
fileName = newTexName
if newTexName == None:
return 0
bTexAsSource = False
newTEX = rapi.loadIntoByteArray(newTexName)
oldDDS = rapi.loadIntoByteArray(rapi.getInputName())
f = NoeBitStream(newTEX)
og = NoeBitStream(oldDDS)
magic = f.readUInt()
version = f.readUInt()
fWidth = f.readUShort()
fHeight = f.readUShort()
reVerseSize = 0
version = convertTexVersion(version)
f.seek(14)
if version > 27:
reVerseSize = 8
numImages = f.readUByte()