-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathMedia.bb
More file actions
1140 lines (937 loc) · 28.7 KB
/
Media.bb
File metadata and controls
1140 lines (937 loc) · 28.7 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
Dim LoadedTextures(65534)
Dim LoadedMeshes(65534)
Dim LoadedMeshScales#(65534)
Dim LoadedMeshX#(65534)
Dim LoadedMeshY#(65534)
Dim LoadedMeshZ#(65534)
Dim LoadedMeshShaders(65534)
Dim LoadedSounds(65534)
Dim TextureFlags(65534)
Global LockedMeshes = 0, LockedTextures = 0, LockedSounds = 0, LockedMusic = 0
Global DefaultVolume# = 1.0
Type LoadedMediaData
Field ID, DataAddress, Name$, ExtraData, Shader
Field Scale#, X#, Y#, Z#
End Type
; Self-contained bounded ReadString$ used by the GetMesh / GetTexture /
; GetSound filename reads below. Module-local so the Tools (RC Architect,
; etc.) can include Media.bb without pulling Logging.bb in for the
; ReadBoundedString$ definition. Same shape: ReadInt prefix, refuse on
; negative or >MaxLen, then byte-by-byte read. No WriteLog dependency.
Function MediaReadFilename$(F, MaxLen)
If F = 0 Then Return ""
Local L = ReadInt(F)
If L < 0 Or L > MaxLen Then Return ""
If L = 0 Then Return ""
Local s$ = ""
Local i
For i = 1 To L
If Eof(F) Then Exit
s$ = s$ + Chr$(ReadByte(F))
Next
Return s$
End Function
; Locks the meshes database (keeps the file open for faster batched Get...() calls)
Function LockMeshes()
LockedMeshes = OpenFile("Data\Game Data\Meshes.dat")
Return LockedMeshes
End Function
; Unlocks the meshes database (closes the file again)
Function UnlockMeshes()
If LockedMeshes <> 0 Then CloseFile LockedMeshes : LockedMeshes = 0 : Return True
End Function
; Locks the textures database (keeps the file open for faster batched Get...() calls)
Function LockTextures()
LockedTextures = OpenFile("Data\Game Data\Textures.dat")
Return LockedTextures
End Function
; Unlocks the textures database (closes the file again)
Function UnlockTextures()
If LockedTextures <> 0 Then CloseFile LockedTextures : LockedTextures = 0 : Return True
End Function
; Locks the sounds database (keeps the file open for faster batched Get...() calls)
Function LockSounds()
LockedSounds = OpenFile("Data\Game Data\Sounds.dat")
Return LockedSounds
End Function
; Unlocks the sounds database (closes the file again)
Function UnlockSounds()
If LockedSounds <> 0 Then CloseFile LockedSounds : LockedSounds = 0 : Return True
End Function
; Locks the music database (keeps the file open for faster batched Get...() calls)
Function LockMusic()
LockedMusic = OpenFile("Data\Game Data\Music.dat")
Return LockedMusic
End Function
; Unlocks the sounds database (closes the file again)
Function UnlockMusic()
If LockedMusic <> 0 Then CloseFile LockedMusic : LockedMusic = 0 : Return True
End Function
; Creates a new (blank) media database.
;
; Atomic via SafeWriteOpen / SafeWriteCommit. The 65535-int zero-init
; produces a ~256KB file; a crash mid-init previously left a truncated
; index that subsequent OpenFile loads would read past EOF on (Blitz
; zero-fills past EOF silently, so missing slots became "ID 0" — a
; class of "Meshes.dat is corrupted" mystery loads). SafeWriteCommit
; refuses to promote an empty temp and keeps any prior version as .bak.
Function CreateDatabase(Filename$)
Local TempPath$ = SafeWriteOpen$(Filename$)
F = WriteFile(TempPath$)
If F = 0 Then Return False
For ID = 0 To 65534
WriteInt F, 0
Next
Return SafeWriteCommit%(TempPath$, Filename$, F)
End Function
; Removes a mesh from the database (slow)
Function RemoveMeshFromDatabase(ID)
UnloadMesh(ID)
; Open index file
If LockedMeshes = 0
F = OpenFile("Data\Game Data\Meshes.dat")
If F = 0 Then Return False
Else
F = LockedMeshes
EndIf
; Read in the data address for every ID except this one
For i = 0 To 65534
DataAddress = ReadInt(F)
If DataAddress > 0 And i <> ID
L.LoadedMediaData = New LoadedMediaData
L\ID = i
L\DataAddress = DataAddress
EndIf
Next
; Read in the actual data for each existing ID
For L.LoadedMediaData = Each LoadedMediaData
SeekFile F, L\DataAddress
L\ExtraData = ReadByte(F)
L\Scale# = ReadFloat#(F)
L\X# = ReadFloat#(F)
L\Y# = ReadFloat#(F)
L\Z# = ReadFloat#(F)
L\Shader = ReadShort(F)
L\Name$ = MediaReadFilename$(F, 260)
Next
; Clear the database
CloseFile(F)
Result = CreateDatabase("Data\Game Data\Meshes.dat")
If Result = False Then LockedMeshes = 0 : Return False
; Reopen it
F = OpenFile("Data\Game Data\Meshes.dat")
If LockedMeshes <> 0 Then LockedMeshes = F
; Write everything back out again
FileEnd = 65535 * 4
For L.LoadedMediaData = Each LoadedMediaData
SeekFile F, L\ID * 4
WriteInt F, FileEnd
SeekFile F, FileEnd
WriteByte F, L\ExtraData
WriteFloat F, L\Scale#
WriteFloat F, L\X#
WriteFloat F, L\Y#
WriteFloat F, L\Z#
WriteShort F, L\Shader
WriteString F, L\Name$
FileEnd = FilePos(F)
Next
Delete Each LoadedMediaData
If LockedMeshes = 0 Then CloseFile(F)
Return True
End Function
; Removes a texture from the database (slow)
Function RemoveTextureFromDatabase(ID)
UnloadTexture(ID)
; Open index file
If LockedTextures = 0
F = OpenFile("Data\Game Data\Textures.dat")
If F = 0 Then Return False
Else
F = LockedTextures
EndIf
; Read in the data address for every ID except this one
For i = 0 To 65534
DataAddress = ReadInt(F)
If DataAddress > 0 And i <> ID
L.LoadedMediaData = New LoadedMediaData
L\ID = i
L\DataAddress = DataAddress
EndIf
Next
; Read in the actual data for each existing ID
For L.LoadedMediaData = Each LoadedMediaData
SeekFile F, L\DataAddress
L\ExtraData = ReadShort(F)
L\Name$ = MediaReadFilename$(F, 260)
Next
; Clear the database
CloseFile(F)
Result = CreateDatabase("Data\Game Data\Textures.dat")
If Result = False Then LockedTextures = 0 : Return False
; Reopen it
F = OpenFile("Data\Game Data\Textures.dat")
If LockedTextures <> 0 Then LockedTextures = F
; Write everything back out again
FileEnd = 65535 * 4
For L.LoadedMediaData = Each LoadedMediaData
SeekFile F, L\ID * 4
WriteInt F, FileEnd
SeekFile F, FileEnd
WriteShort F, L\ExtraData
WriteString F, L\Name$
FileEnd = FilePos(F)
Next
Delete Each LoadedMediaData
If LockedTextures = 0 Then CloseFile(F)
Return True
End Function
; Removes a sound from the database (slow)
Function RemoveSoundFromDatabase(ID)
UnloadSound(ID)
; Open index file
If LockedSounds = 0
F = OpenFile("Data\Game Data\Sounds.dat")
If F = 0 Then Return False
Else
F = LockedSounds
EndIf
; Read in the data address for every ID except this one
For i = 0 To 65534
DataAddress = ReadInt(F)
If DataAddress > 0 And i <> ID
L.LoadedMediaData = New LoadedMediaData
L\ID = i
L\DataAddress = DataAddress
EndIf
Next
; Read in the actual data for each existing ID
For L.LoadedMediaData = Each LoadedMediaData
SeekFile F, L\DataAddress
L\ExtraData = ReadByte(F)
L\Name$ = MediaReadFilename$(F, 260)
Next
; Clear the database
CloseFile(F)
Result = CreateDatabase("Data\Game Data\Sounds.dat")
If Result = False Then LockedSounds = 0 : Return False
; Reopen it
F = OpenFile("Data\Game Data\Sounds.dat")
If LockedSounds <> 0 Then LockedSounds = F
; Write everything back out again
FileEnd = 65535 * 4
For L.LoadedMediaData = Each LoadedMediaData
SeekFile F, L\ID * 4
WriteInt F, FileEnd
SeekFile F, FileEnd
WriteByte F, L\ExtraData
WriteString F, L\Name$
FileEnd = FilePos(F)
Next
Delete Each LoadedMediaData
If LockedSounds = 0 Then CloseFile(F)
Return True
End Function
; Removes music from the database (slow)
Function RemoveMusicFromDatabase(ID)
; Open index file
If LockedMusic = 0
F = OpenFile("Data\Game Data\Music.dat")
If F = 0 Then Return False
Else
F = LockedMusic
EndIf
; Read in the data address for every ID except this one
For i = 0 To 65534
DataAddress = ReadInt(F)
If DataAddress > 0 And i <> ID
L.LoadedMediaData = New LoadedMediaData
L\ID = i
L\DataAddress = DataAddress
EndIf
Next
; Read in the actual data for each existing ID
For L.LoadedMediaData = Each LoadedMediaData
SeekFile F, L\DataAddress
L\Name$ = MediaReadFilename$(F, 260)
Next
; Clear the database
CloseFile(F)
Result = CreateDatabase("Data\Game Data\Music.dat")
If Result = False Then LockedMusic = 0 : Return False
; Reopen it
F = OpenFile("Data\Game Data\Music.dat")
If LockedMusic <> 0 Then LockedMusic = F
; Write everything back out again
FileEnd = 65535 * 4
For L.LoadedMediaData = Each LoadedMediaData
SeekFile F, L\ID * 4
WriteInt F, FileEnd
SeekFile F, FileEnd
WriteString F, L\Name$
FileEnd = FilePos(F)
Next
Delete Each LoadedMediaData
If LockedMusic = 0 Then CloseFile(F)
Return True
End Function
; Adds a new mesh to the database and returns its ID
Function AddMeshToDatabase(Filename$, IsAnim)
; Open index file
If LockedMeshes = 0
F = OpenFile("Data\Game Data\Meshes.dat")
If F = 0 Then Return -1
Else
F = LockedMeshes
EndIf
; Check every mesh to make sure this one isn't already there
SeekFile F, (65535 * 4)
While Eof(F) = False
MIsAnim = ReadByte(F)
ReadFloat#(F)
ReadFloat#(F)
ReadFloat#(F)
ReadFloat#(F)
ReadShort(F)
Name$ = MediaReadFilename$(F, 260)
; If this mesh is already in the file, return error
If (Upper$(Name$) = Upper$(Filename$)) And (MIsAnim = IsAnim)
If LockedMeshes = 0 Then CloseFile(F)
Return -1
EndIf
Wend
; Find the first free ID
SeekFile F, 0
For ID = 0 To 65534
DataAddress = ReadInt(F)
If DataAddress = 0
; Write mesh data to index file
SeekFile F, ID * 4
WriteInt F, FileSize("Data\Game Data\Meshes.dat")
SeekFile F, FileSize("Data\Game Data\Meshes.dat")
WriteByte F, IsAnim
WriteFloat F, 1.0
WriteFloat F, 0.0
WriteFloat F, 0.0
WriteFloat F, 0.0
WriteShort F, 65535
WriteString F, Filename$
If LockedMeshes = 0 Then CloseFile(F)
; Return new ID
Return ID
EndIf
Next
; No free ID found
If LockedMeshes = 0 Then CloseFile(F)
Return -1
End Function
; Adds a new texture to the database and returns its ID
Function AddTextureToDatabase(Filename$, Flags)
; Open index file
If LockedTextures = 0
F = OpenFile("Data\Game Data\Textures.dat")
If F = 0 Then Return -1
Else
F = LockedTextures
EndIf
; Check every texture to make sure this one isn't already there
SeekFile(F, 65535 * 4)
While Eof(F) = False
TFlags = ReadShort(F)
Name$ = MediaReadFilename$(F, 260)
; If this texture is already in the file, return error
If (Upper$(Name$) = Upper$(Filename$)) And (TFlags = Flags)
If LockedTextures = 0 Then CloseFile(F)
Return -1
EndIf
Wend
; Find the first free ID
SeekFile F, 0
For ID = 0 To 65534
DataAddress = ReadInt(F)
If DataAddress = 0
; Write texture data to index file
SeekFile(F, ID * 4)
WriteInt(F, FileSize("Data\Game Data\Textures.dat"))
SeekFile(F, FileSize("Data\Game Data\Textures.dat"))
WriteShort(F, Flags)
WriteString(F, Filename$)
If LockedTextures = 0 Then CloseFile(F)
; Return new ID
Return ID
EndIf
Next
; No free ID found
If LockedTextures = 0 Then CloseFile(F)
Return -1
End Function
; Adds a new sound to the database and returns its ID
Function AddSoundToDatabase(Filename$, Is3D)
; Open index file
If LockedSounds = 0
F = OpenFile("Data\Game Data\Sounds.dat")
If F = 0 Then Return -1
Else
F = LockedSounds
EndIf
; Check every sound to make sure this one isn't already there
SeekFile(F, 65535 * 4)
While Eof(F) = False
SIs3D = ReadByte(F)
Name$ = MediaReadFilename$(F, 260)
; If this sound is already in the file, return error
If (Upper$(Name$) = Upper$(Filename$)) And (SIs3D = Is3D)
If LockedSounds = 0 Then CloseFile(F)
Return -1
EndIf
Wend
; Find the first free ID
SeekFile F, 0
For ID = 0 To 65534
DataAddress = ReadInt(F)
If DataAddress = 0
; Write sound data to index file
SeekFile F, ID * 4
WriteInt F, FileSize("Data\Game Data\Sounds.dat")
SeekFile F, FileSize("Data\Game Data\Sounds.dat")
WriteByte F, Is3D
WriteString F, Filename$
If LockedSounds = 0 Then CloseFile(F)
; Return new ID
Return ID
EndIf
Next
; No free ID found
If LockedSounds = 0 Then CloseFile(F)
Return -1
End Function
; Adds new music to the database and returns its ID
Function AddMusicToDatabase(Filename$)
; Open index file
If LockedMusic = 0
F = OpenFile("Data\Game Data\Music.dat")
If F = 0 Then Return -1
Else
F = LockedMusic
EndIf
; Check all music to make sure this one isn't already there
SeekFile F, (65535 * 4)
While Eof(F) = False
Name$ = MediaReadFilename$(F, 260)
; If this music is already in the file, return error. Previously
; the dup branch closed the file but then fell through to the
; insert path below -- so every duplicate was added a second time.
If Upper$(Name$) = Upper$(Filename$)
If LockedMusic = 0 Then CloseFile(F)
Return -1
EndIf
Wend
; Find the first free ID
SeekFile F, 0
For ID = 0 To 65534
DataAddress = ReadInt(F)
If DataAddress = 0
; Write sound data to index file
SeekFile F, ID * 4
WriteInt F, FileSize("Data\Game Data\Music.dat")
SeekFile F, FileSize("Data\Game Data\Music.dat")
WriteString F, Filename$
; Return new ID
If LockedMusic = 0 Then CloseFile(F)
Return ID
EndIf
Next
; No free ID found
If LockedMusic = 0 Then CloseFile(F)
Return -1
End Function
; Gets the name and animation byte for a given mesh
Function GetMeshName$(ID)
; Mirror GetMesh's bound check. The sibling getter does the same.
; Negative ID makes SeekFile go to a negative offset (undefined);
; > 65534 walks past the index table. Sentinel IDs like -1 (used
; by some Actors paths to mean "no mesh") would otherwise crash
; the client every frame an actor with that sentinel renders.
If ID < 0 Or ID > 65534 Then Return ""
; Open index file
If LockedMeshes = 0
F = OpenFile("Data\Game Data\Meshes.dat")
If F = 0 Then Return ""
Else
F = LockedMeshes
EndIf
; Find data address in file index
SeekFile(F, ID * 4)
DataAddress = ReadInt(F)
If DataAddress = 0
If LockedMeshes = 0 Then CloseFile(F)
Return ""
EndIf
; Read in mesh data
SeekFile(F, DataAddress)
IsAnim = ReadByte(F)
ReadFloat#(F)
ReadFloat#(F)
ReadFloat#(F)
ReadFloat#(F)
ReadShort(F)
Name$ = MediaReadFilename$(F, 260)
If LockedMeshes = 0 Then CloseFile(F)
Return Name$ + Chr$(IsAnim)
End Function
; Gets the name and flags for a given texture
Function GetTextureName$(ID)
; Mirror GetTexture's bound check (line 844).
If ID < 0 Or ID > 65534 Then Return ""
; Open index file
If LockedTextures = 0
F = OpenFile("Data\Game Data\Textures.dat")
If F = 0 Then Return ""
Else
F = LockedTextures
EndIf
; Find data address in file index
SeekFile F, ID * 4
DataAddress = ReadInt(F)
If DataAddress = 0
If LockedTextures = 0 Then CloseFile(F)
Return ""
EndIf
; Read in texture data
SeekFile F, DataAddress
Flags = ReadShort(F)
Name$ = MediaReadFilename$(F, 260)
If LockedTextures = 0 Then CloseFile(F)
Return Name$ + Chr$(Flags)
End Function
; Gets the name and 3D byte for a given sound
Function GetSoundName$(ID)
; Mirror GetSound's bound check (line 891).
If ID < 0 Or ID > 65534 Then Return ""
; Open index file
If LockedSounds = 0
F = OpenFile("Data\Game Data\Sounds.dat")
If F = 0 Then Return ""
Else
F = LockedSounds
EndIf
; Find data address in file index
SeekFile F, ID * 4
DataAddress = ReadInt(F)
If DataAddress = 0
If LockedSounds = 0 Then CloseFile(F)
Return ""
EndIf
; Read in sound data
SeekFile F, DataAddress
Is3D = ReadByte(F)
Name$ = MediaReadFilename$(F, 260)
If LockedSounds = 0 Then CloseFile(F)
Return Name$ + Chr$(Is3D)
End Function
; Gets the name of a given piece of music
Function GetMusicName$(ID)
; Music IDs are also indexed into a 4-byte-per-entry Data/Game
; Data/Music.dat table. Same bound as GetSound / GetMesh / etc.
If ID < 0 Or ID > 65534 Then Return ""
; Open index file
If LockedMusic = 0
F = OpenFile("Data\Game Data\Music.dat")
If F = 0 Then Return ""
Else
F = LockedMusic
EndIf
; Find data address in file index
SeekFile F, ID * 4
DataAddress = ReadInt(F)
If DataAddress = 0
If LockedMusic = 0 Then CloseFile(F)
Return ""
EndIf
; Read in sound data
SeekFile F, DataAddress
Name$ = MediaReadFilename$(F, 260)
If LockedMusic = 0 Then CloseFile(F)
Return Name$
End Function
; Changes the scale for a mesh
Function SetMeshScale(ID, Scale#)
; LoadedMeshScales# is Dim'd 0..65534. Mirror GetMesh's bound check
; (line 756) -- editor-supplied IDs from MediaDialogs can land here
; via the Architect's mesh-config flow; a typo or sentinel would
; otherwise OOB-write into adjacent globals.
If ID < 0 Or ID > 65534 Then Return False
LoadedMeshScales#(ID) = Scale#
; Open index file
If LockedMeshes = 0
F = OpenFile("Data\Game Data\Meshes.dat")
If F = 0 Then Return False
Else
F = LockedMeshes
EndIf
; Find data address in file index
SeekFile F, ID * 4
DataAddress = ReadInt(F)
If DataAddress = 0
If LockedMeshes = 0 Then CloseFile(F)
Return False
EndIf
; Write new scale float
SeekFile F, DataAddress + 1
WriteFloat F, Scale#
If LockedMeshes = 0 Then CloseFile(F)
Return True
End Function
; Changes the offset for a mesh
Function SetMeshOffset(ID, X#, Y#, Z#)
; LoadedMeshX#/Y#/Z# are Dim'd 0..65534. Same bound as SetMeshScale.
If ID < 0 Or ID > 65534 Then Return False
LoadedMeshX#(ID) = X#
LoadedMeshY#(ID) = Y#
LoadedMeshZ#(ID) = Z#
; Open index file
If LockedMeshes = 0
F = OpenFile("Data\Game Data\Meshes.dat")
If F = 0 Then Return False
Else
F = LockedMeshes
EndIf
; Find data address in file index
SeekFile F, ID * 4
DataAddress = ReadInt(F)
If DataAddress = 0
If LockedMeshes = 0 Then CloseFile(F)
Return False
EndIf
; Write new scale float
SeekFile F, DataAddress + 5
WriteFloat F, X#
WriteFloat F, Y#
WriteFloat F, Z#
If LockedMeshes = 0 Then CloseFile(F)
Return True
End Function
; Changes the shader for a mesh
Function SetMeshShader(ID, Shader)
; LoadedMeshShaders is Dim'd 0..65534. Same bound as SetMeshScale.
If ID < 0 Or ID > 65534 Then Return False
LoadedMeshShaders(ID) = Shader
; Open index file
If LockedMeshes = 0
F = OpenFile("Data\Game Data\Meshes.dat")
If F = 0 Then Return False
Else
F = LockedMeshes
EndIf
; Find data address in file index
SeekFile F, ID * 4
DataAddress = ReadInt(F)
If DataAddress = 0
If LockedMeshes = 0 Then CloseFile(F)
Return False
EndIf
; Write new scale float
SeekFile F, DataAddress + 17
WriteShort(F, Shader)
If LockedMeshes = 0 Then CloseFile(F)
Return True
End Function
; Gets the handle for a given mesh (this will load it if it isn't present)
Function GetMesh(ID, Duplicate = False)
; Mirror GetSound's ID guard. LoadedMeshes is Dim'd 0..65534; a caller
; passing -1 (the inconsistent sentinel some Actors3D paths use instead
; of 65535) or any out-of-range ID would write through a wild pointer.
If ID < 0 Or ID > 65534 Then Return 0
; Load from file if this is the first time the mesh has been loaded
If LoadedMeshes(ID) = 0
; Read in filename and other data from index file
If LockedMeshes = 0
F = OpenFile("Data\Game Data\Meshes.dat")
If F = 0 Then Return 0
Else
F = LockedMeshes
EndIf
; Find data address in file index
SeekFile F, ID * 4
DataAddress = ReadInt(F)
If DataAddress = 0
If LockedMeshes = 0 Then CloseFile(F)
Return 0
EndIf
; Read in mesh data. Bound the filename read so a corrupted
; Meshes.dat (or one tampered with via the update channel)
; can't hang the client allocating gigabytes on a wild
; length prefix. 260 covers a generous max path length.
SeekFile F, DataAddress
IsAnim = ReadByte(F)
LoadedMeshScales#(ID) = ReadFloat#(F)
LoadedMeshX#(ID) = ReadFloat#(F)
LoadedMeshY#(ID) = ReadFloat#(F)
LoadedMeshZ#(ID) = ReadFloat#(F)
LoadedMeshShaders(ID) = ReadShort(F)
Name$ = MediaReadFilename$(F, 260)
If LockedMeshes = 0 Then CloseFile(F)
; Reject path-traversal in Meshes.dat-stored names. The update
; channel can rewrite Meshes.dat, so a hostile update payload could
; plant `..\..\<x>` and force a LoadMesh on an arbitrary file.
; LoadMesh would mostly return 0 on miss, but the attempt itself is
; the wrong shape -- bail before we touch the filesystem.
If Instr(Name$, "..") > 0 Then Return 0
; Load the mesh
If IsAnim = True
LoadedMeshes(ID) = LoadAnimMesh("Data\Meshes\" + Name$)
If LoadedMeshes(ID) = 0 Then Return(0)
HideEntity LoadedMeshes(ID)
ScaleEntity LoadedMeshes(ID), LoadedMeshScales#(ID), LoadedMeshScales#(ID), LoadedMeshScales#(ID)
Else
LoadedMeshes(ID) = LoadMesh("Data\Meshes\" + Name$)
If LoadedMeshes(ID) = 0 Then Return(0)
HideEntity(LoadedMeshes(ID))
ScaleEntity(LoadedMeshes(ID), LoadedMeshScales#(ID), LoadedMeshScales#(ID), LoadedMeshScales#(ID))
EndIf
EndIf
; Return a copy of the entity
If Duplicate = False
Return(CopyEntity(LoadedMeshes(ID)))
; Duplicate a mesh - required for actors because annoyingly Blitz textures are mesh, not entity, specific
Else
Name$ = GetMeshName$(ID)
IsAnim = Asc(Right$(Name$, 1))
Name$ = Left$(Name$, Len(Name$) - 1)
If Lower(Right(Name$, 4)) = ".eb3d" Then
MessageBox 0, "Cannot load eb3d files!", "Mesh Error", 16
End If
If IsAnim = True
EN = LoadAnimMesh("Data\Meshes\" + Name$)
If EN = 0 Then Return(0)
ScaleEntity(EN, LoadedMeshScales#(ID), LoadedMeshScales#(ID), LoadedMeshScales#(ID))
Else
EN = LoadMesh("Data\Meshes\" + Name$)
If EN = 0 Then Return(0)
ScaleEntity(EN, LoadedMeshScales#(ID), LoadedMeshScales#(ID), LoadedMeshScales#(ID))
EndIf
Return(EN)
EndIf
End Function
; Gets the handle for a given texture (this will load it if it isn't present)
Function GetTexture(ID, Copy = False)
If ID < 0 Or ID > 65534 Then Return 0
If LoadedTextures(ID) = 0
; Read in filename and other data from index file
If LockedTextures = 0
F = OpenFile("Data\Game Data\Textures.dat")
If F = 0 Then Return -1
Else
F = LockedTextures
EndIf
; Find data address in file index
SeekFile(F, ID * 4)
DataAddress = ReadInt(F)
If DataAddress = 0
If LockedTextures = 0 Then CloseFile(F)
Return 0
EndIf
; Read in texture data. Bound filename + path-traversal guard
; mirror GetMesh's defense: an update-channel payload could
; otherwise plant `..\..\<x>` and force LoadTexture against an
; arbitrary file. LoadTexture won't execute the contents, but
; reading from outside Data\Textures\ is the wrong shape.
SeekFile(F, DataAddress)
Flags = ReadShort(F)
Name$ = MediaReadFilename$(F, 260)
If LockedTextures = 0 Then CloseFile(F)
If Instr(Name$, "..") > 0 Then Return 0
LoadedTextures(ID) = LoadTexture("Data\Textures\" + Name$, Flags)
TextureFlags(ID) = Flags
EndIf
If Copy = True
If LoadedTextures(ID) = 0 Then Return 0
Return CopyTexture(LoadedTextures(ID), TextureFlags(ID))
Else
Return LoadedTextures(ID)
EndIf
End Function
; Gets the handle for a given sound (this will load it if it isn't present)
Function GetSound(ID)
If ID < 0 Or ID > 65534 Then Return 0
If LoadedSounds(ID) = 0
; Read in filename and other data from index file
If LockedSounds = 0
F = OpenFile("Data\Game Data\Sounds.dat")
If F = 0 Then Return -1
Else
F = LockedSounds
EndIf
; Find data address in file index
SeekFile F, ID * 4
DataAddress = ReadInt(F)
If DataAddress = 0
If LockedSounds = 0 Then CloseFile(F)
Return 0
EndIf
; Read in sound data. Bound filename + path-traversal guard
; mirror GetMesh / GetTexture (see above).
SeekFile F, DataAddress
Is3D = ReadByte(F)
Name$ = MediaReadFilename$(F, 260)
If LockedSounds = 0 Then CloseFile(F)
If Instr(Name$, "..") > 0 Then Return 0
LoadedSounds(ID) = LoadSound("Data\Sounds\" + Name$, Is3D)
EndIf
Return LoadedSounds(ID)
End Function
; Unloads a mesh
Function UnloadMesh(ID)
; Check for correct input
If ID < 0 Or ID > 65534 Then Return
If LoadedMeshes(ID) <> 0 Then FreeEntity LoadedMeshes(ID)
LoadedMeshes(ID) = 0
End Function
; Unloads a texture
Function UnloadTexture(ID)
; Check for correct input
If ID < 0 Or ID > 65534 Then Return
If LoadedTextures(ID) <> 0 Then FreeTexture LoadedTextures(ID)
LoadedTextures(ID) = 0
End Function
; Unloads a sound
Function UnloadSound(ID)
; Check for correct input
If ID < 0 Or ID > 65534 Then Return
If LoadedSounds(ID) <> 0 Then FreeSound LoadedSounds(ID)
LoadedSounds(ID) = 0
End Function
; Scales a mesh entity to be a certain size without altering the mesh (works on animated meshes)
Function SizeEntity(EN, Width#, Height#, Depth#, Uniform = False)
; Find mesh edges
Result.MeshMinMaxVertices = MeshMinMaxVertices(EN)
MWidth# = Result\MaxX# - Result\MinX#
MHeight# = Result\MaxY# - Result\MinY#
MDepth# = Result\MaxZ# - Result\MinZ#
Delete Result
; Scale
If Uniform = False