-
Notifications
You must be signed in to change notification settings - Fork 1
/
datup.py
289 lines (246 loc) · 12.8 KB
/
datup.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
import argparse
import sys
import struct
import io
import os
from enum import Enum
# Setting all the available arguments
parser = argparse.ArgumentParser()
parser.add_argument("-i", "--input", help="Path and file name of the DAT to read from")
args = parser.parse_args()
# Values for DEBUG part
currentByte = 0;
currentOffset = 0;
# Not needed
currentBit = 8;
# Reading content of the DAT file
class DATfile:
def parse(self,bytes_):
# World Header
self.Version = int.from_bytes(bytes_.read(4), 'little', signed=False)
self.ObjectDataPosition = int.from_bytes(bytes_.read(4), 'little', signed=False)
self.RenderDataPosition = int.from_bytes(bytes_.read(4), 'little', signed=False)
self.Dummy = int.from_bytes(bytes_.read(32), 'little', signed=False)
# WorldInfo
self.WorldInfoLength = int.from_bytes(bytes_.read(4), 'little', signed=False)
self.WorldInfoValue = bytes_.read(self.WorldInfoLength).decode()
self.LMGridSize = struct.unpack("<f",bytes_.read(4))[0]
self.BoundaryMinX = struct.unpack("<f",bytes_.read(4))[0]
self.BoundaryMinY = struct.unpack("<f",bytes_.read(4))[0]
self.BoundaryMinZ = struct.unpack("<f",bytes_.read(4))[0]
self.BoundaryMaxX = struct.unpack("<f",bytes_.read(4))[0]
self.BoundaryMaxY = struct.unpack("<f",bytes_.read(4))[0]
self.BoundaryMaxZ = struct.unpack("<f",bytes_.read(4))[0]
# WorldTree
self.BoxMin = bytes_.read(12)
self.BoxMax = bytes_.read(12)
self.NumNodes = int.from_bytes(bytes_.read(4), 'little', signed=True)
self.DummyTerrainDepth = int.from_bytes(bytes_.read(4), 'little', signed=True)
# DEBUG Part. We just wait while DEBUG will and with 8 and 1
self.DEBUGCount = 1
self.DEBUGValue = [int.from_bytes(bytes_.read(1), 'little', signed=True)]
self.DEBUGValue.append(int.from_bytes(bytes_.read(1), 'little', signed=True))
while (self.DEBUGValue[self.DEBUGCount-1] !=8 or self.DEBUGValue[self.DEBUGCount] != 1):
self.DEBUGCount +=1
self.DEBUGValue.append(int.from_bytes(bytes_.read(1), 'little', signed=True))
# Model Header
self.NumModels = int.from_bytes(bytes_.read(4), 'little', signed=True)
self.NextWorldItem = []
self.Padding = []
self.InfoFlags = []
self.ModelNameLength = []
self.ModelNameValue = []
self.PointCount = []
self.PlaneCount = []
self.SurfaceCount = []
self.ModelDATA = []
self.UserPortalCount = []
self.PolyCount = []
self.LeafCount = []
self.VertCount = []
self.TotalVisListSize = []
self.LeafListCount = []
self.NodeCount = []
self.TextureLength = []
self.TextureCount = []
self.TextureName = []
self.Vertex = []
self.Plane = []
i = 0
for i in range(self.NumModels):
self.NextWorldItem.append(int.from_bytes(bytes_.read(4), 'little', signed=True))
self.Padding.append(bytes_.read(32))
self.InfoFlags.append(bytes_.read(8))
self.ModelNameLength.append(int.from_bytes(bytes_.read(2), 'little', signed=True))
#self.ModelNameValue.append(bytes_.read(self.ModelNameLength[i]).decode())
self.ModelNameValue.append(bytes_.read(self.ModelNameLength[i]))
self.PointCount.append(int.from_bytes(bytes_.read(4), 'little', signed=True))
self.PlaneCount.append(int.from_bytes(bytes_.read(4), 'little', signed=True))
self.SurfaceCount.append(int.from_bytes(bytes_.read(4), 'little', signed=True))
self.UserPortalCount.append(int.from_bytes(bytes_.read(4), 'little', signed=True))
self.PolyCount.append(int.from_bytes(bytes_.read(4), 'little', signed=True))
self.LeafCount.append(int.from_bytes(bytes_.read(4), 'little', signed=True))
self.VertCount.append(int.from_bytes(bytes_.read(4), 'little', signed=True))
self.TotalVisListSize.append(int.from_bytes(bytes_.read(4), 'little', signed=True))
self.LeafListCount.append(int.from_bytes(bytes_.read(4), 'little', signed=True))
self.NodeCount.append(int.from_bytes(bytes_.read(4), 'little', signed=True))
self.ModelDATA.append(bytes_.read(44))
self.TextureLength.append(int.from_bytes(bytes_.read(4), 'little', signed=True))
self.TextureCount.append(int.from_bytes(bytes_.read(4), 'little', signed=True))
#self.TextureName.append(bytes_.read(self.TextureLength[i]).decode())
self.TextureName.append(bytes_.read(self.TextureLength[i]))
# Vertex Loop
j = 0
for j in range(self.PolyCount[i]):
self.Vertex.append(bytes_.read(2))
# Plane Loop
j = 0
for j in range(self.PlaneCount[i]):
self.Plane.append(bytes_.read(16))
# Reading input file
input_file=open(args.input, 'rb')
# Reading header like a stream of bytes and parsing
header = DATfile()
header.parse(io.BytesIO(input_file.read()))
print("Version: {}".format(header.Version))
print(header.ObjectDataPosition)
print(header.RenderDataPosition)
print(header.Dummy)
print("World Info Length: {}".format(header.WorldInfoLength))
print("World Info Value: {}".format(header.WorldInfoValue))
print("LMGridSize: {}".format(header.LMGridSize))
print("Boundary Min (X/Y/Z): {} / {} / {}".format(header.BoundaryMinX,header.BoundaryMinY,header.BoundaryMinZ))
print("Boundary Max (X/Y/Z): {} / {} / {}".format(header.BoundaryMaxX,header.BoundaryMaxY,header.BoundaryMaxZ))
print("Number of Nodes: {}".format(header.NumNodes))
print("DummyTerrainDepth: {}".format(header.DummyTerrainDepth))
print("DEBUG Value: {}".format(header.DEBUGValue))
print(header.NumModels)
print(header.NextWorldItem)
print(header.ModelNameLength)
#print(header.ModelNameValue)
print(header.SurfaceCount)
print(header.TextureName[0])
print(header.Vertex[0])
"""
read_layout(currentByte, currentBit, currentOffset, 0);
WorldModelHeader modelHeader;
WorldObjectHeader objectHeader;
# Defining BPP enumeration values
class BPP_Enum(Enum):
BPP_8P = 0
BPP_8 = 1
BPP_16 = 2
BPP_32 = 3
BPP_S3TC_DXT1 = 4
BPP_S3TC_DXT3 = 5
BPP_S3TC_DXT5 = 6
BPP_32P = 7
BPP_24 = 8
# Defining DTX version enumeration values
class DTX_ver_Enum(Enum):
DTX_VERSION_LT1 = -2
DTX_VERSION_LT15 = -3
DTX_VERSION_LT2 = -5
# Reading header of the file. Thanks to Amphos
class DtxHeader(object):
def __init__(self): # called on creation, set up some sane defaults
self.filetype = 0
self.version = -5
self.width = -1
self.height = -1
self.mipmaps = 4
# Parsing the whole header like a stream of bytes using research for DTX v2
def parse(self, bytes_):
self.filetype = int.from_bytes(bytes_.read(4), 'little', signed=False)
self.version = int.from_bytes(bytes_.read(4), 'little', signed=True)
self.width = int.from_bytes(bytes_.read(2), 'little', signed=False)
self.height = int.from_bytes(bytes_.read(2), 'little', signed=False)
self.mipmaps_default = int.from_bytes(bytes_.read(2), 'little', signed=False) # always 4
self.light_flag = int.from_bytes(bytes_.read(2), 'little', signed=False)
# Parsing DTX Flags
self.dtx_flags = "{:08b}".format(int.from_bytes(bytes_.read(1), 'little', signed=False)) + "{:08b}".format(int.from_bytes(bytes_.read(1), 'little', signed=False))
# Bit Flags for DTX Flags
self.DTX_PREFER4444="DTX_PREFER4444 " if int(self.dtx_flags[0]) else ""
self.DTX_NOSYSCACHE="DTX_NOSYSCACHE " if int(self.dtx_flags[1]) else ""
self.DTX_SECTIONSFIXED="DTX_SECTIONSFIXED " if int(self.dtx_flags[4]) else ""
self.DTX_MIPSALLOCED="DTX_MIPSALLOCED " if int(self.dtx_flags[5]) else ""
self.DTX_PREFER16BIT="DTX_PREFER16BIT " if int(self.dtx_flags[6]) else ""
self.DTX_FULLBRITE="DTX_FULLBRITE " if int(self.dtx_flags[7]) else ""
self.DTX_LUMBUMPMAP="DTX_LUMBUMPMAP " if int(self.dtx_flags[11]) else ""
self.DTX_BUMPMAP="DTX_BUMPMAP " if int(self.dtx_flags[12]) else ""
self.DTX_CUBEMAP="DTX_CUBEMAP " if int(self.dtx_flags[13]) else ""
self.DTX_32BITSYSCOPY="DTX_32BITSYSCOPY " if int(self.dtx_flags[14]) else ""
self.DTX_PREFER5551="DTX_PREFER5551 " if int(self.dtx_flags[15]) else ""
# Everything else
self.unknown = int.from_bytes(bytes_.read(2), 'little', signed=False)
self.surface_flag = int.from_bytes(bytes_.read(4), 'little', signed=True)
self.texture_group = int.from_bytes(bytes_.read(1), 'little', signed=False)
# if we are using 1-3 mipmaps instead of 4
self.mipmaps_used = int.from_bytes(bytes_.read(1), 'little', signed=True)
self.mipmaps_used = 4 if self.mipmaps_used == 0 else self.mipmaps_used
self.bpp = int.from_bytes(bytes_.read(1), 'little', signed=True)
self.non_s3tc_offset = int.from_bytes(bytes_.read(1), 'little', signed=False)
self.ui_mipmap_offset = int.from_bytes(bytes_.read(1), 'little', signed=False)
self.texture_priority = int.from_bytes(bytes_.read(1), 'little', signed=True)
self.detail_scale = struct.unpack("<f",bytes_.read(4))[0]
self.detail_angle = int.from_bytes(bytes_.read(2), 'little', signed=True)
self.command_raw = bytes_.read(128)
self.command_string = self.command_raw.decode()
self.command_string = "" if int(self.command_string[0] == 0) else self.command_string
# If light_flag is 1, we find LIGHTDEFS definition and read all the bytes to the end of file starting from 32nd byte
# it's always 9 bytes of LIGHTDEF and 23 bytes of random data before the real information starting
# Last byte is always 00 in case of light_flag/LIGHTDEF present in file, so we must exclude it for printing
if self.light_flag == 1:
# Reading the rest of the file after header
self.file_data = bytes_.read()[164:]
# Finding and reading tail of the file starting from LIGHTDEFS
self.lightdef_raw = self.file_data[self.file_data.find(b'LIGHTDEFS'):]
self.lightdef_string = self.lightdef_raw[32:-1].decode()
else:
self.lightdef_string = ""
# Reading input file
input_file=open(args.input, 'rb')
# Reading header like a stream of bytes and parsing
header = DtxHeader()
header.parse(io.BytesIO(input_file.read()))
# Dealing with errors of wrong file type or wrong DTX version
if header.filetype != 0:
print("Wrong file type, not a DTX texture")
exit()
if header.filetype == 0 and header.version != -5:
print("Wrong DTX version")
exit()
# For --read argument printing file information
if args.read:
print("File Path: {}".format(args.input))
print("File Type: {}, DTX version: {}, Size: {}x{}, Mipmaps Used: {}, Light Flag: {}".format(header.filetype, DTX_ver_Enum(header.version).name, header.width, header.height, header.mipmaps_used, header.light_flag))
print("DTX Flags: {}: {}{}{}{}{}{}{}{}{}{}{}".format(header.dtx_flags, header.DTX_PREFER4444, header.DTX_NOSYSCACHE, header.DTX_SECTIONSFIXED, header.DTX_MIPSALLOCED, header.DTX_PREFER16BIT, header.DTX_FULLBRITE, header.DTX_LUMBUMPMAP, header.DTX_BUMPMAP, header.DTX_CUBEMAP, header.DTX_32BITSYSCOPY, header.DTX_PREFER5551))
print("Unknown: {}, Surface Flag: {}, Texture Group: {}, BPP: {}".format(header.unknown, header.surface_flag, header.texture_group, BPP_Enum(header.bpp).name))
print("Non S3TC Offset: {}, UI Mipmap Offset: {}, Texture Priority: {}, Detail Scale/Angle: {}/{}".format(header.non_s3tc_offset, header.ui_mipmap_offset, header.texture_priority, header.detail_scale, header.detail_angle))
print("Command String: {}".format(header.command_string))
# Printing only the real data of Light String if it present (starting from 32nd byte and till EOF-1) and decoding to ASCII string
print("Light String: {}".format(header.lightdef_string))
# Transfering meta information between the files
if args.output:
# Opening output file to write to
output_file=open(args.output, 'r+b')
# Setting offset to 12th byte (Number of mipmaps)
input_file.seek(12)
output_file.seek(12)
# Writing first 14 bytes till Number of mipmaps used
output_file.write(input_file.read(14))
# Skipping BPP
input_file.seek(27)
output_file.seek(27)
# Writing everything else till the end of header
output_file.write(input_file.read(137))
output_file.close()
# Writing Light String if it is present
if header.light_flag == 1:
output_file=open(args.output, 'a+')
output_file.write(header.lightdef_raw.decode())
output_file.close()
print("Transfering went successfully from {} to {}".format(args.input, args.output))
# Closing everything
input_file.close()
"""