Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
1 | pmbaty | 1 | #!/usr/bin/python3 |
2 | |||
3 | import collections, json, os, struct |
||
4 | |||
5 | # Various constants from the game engine. These must be kept |
||
6 | # synchronized with the game. |
||
7 | NDL = 5 |
||
8 | MAX_GUNS = 8 |
||
9 | for defines in [ |
||
10 | #common/main/robot.h |
||
11 | """ |
||
12 | #define N_ANIM_STATES 5 |
||
13 | #define MAX_SUBMODELS 10 |
||
14 | #define MAX_ROBOT_TYPES 85 // maximum number of robot types |
||
15 | #define MAX_ROBOT_JOINTS 1600 |
||
16 | """, |
||
17 | #d2x-rebirth/main/weapon.h |
||
18 | """ |
||
19 | #define MAX_WEAPON_TYPES 70 |
||
20 | """, |
||
21 | #common/main/polyobj.h |
||
22 | """ |
||
23 | #define MAX_POLYGON_MODELS 200 |
||
24 | """, |
||
25 | #d2x-rebirth/main/bm.h |
||
26 | """ |
||
27 | #define MAX_OBJ_BITMAPS 610 |
||
28 | """, |
||
29 | #d2x-rebirth/main/bm.c |
||
30 | """ |
||
31 | #define N_D2_ROBOT_TYPES 66 |
||
32 | #define N_D2_ROBOT_JOINTS 1145 |
||
33 | #define N_D2_POLYGON_MODELS 166 |
||
34 | #define N_D2_OBJBITMAPS 422 |
||
35 | #define N_D2_OBJBITMAPPTRS 502 |
||
36 | #define N_D2_WEAPON_TYPES 62 |
||
37 | """, |
||
38 | ]: |
||
39 | for define in defines.strip().split('\n'): |
||
40 | (_, name, value, *_) = define.split() |
||
41 | globals()[name] = int(value) |
||
42 | |||
43 | class SerializeableSingleType: |
||
44 | class Instance: |
||
45 | def __init__(self,descriptor,value): |
||
46 | self.Struct = descriptor |
||
47 | self.value = value |
||
48 | def stwritephys(self,fp,**kwargs): |
||
49 | s = self.Struct |
||
50 | fp.write(s.pack(self.value)) |
||
51 | def streadphys(self,fp,**kwargs): |
||
52 | s = self.Struct |
||
53 | return s.unpack_from(fp.read(s.size)) |
||
54 | def streadjs(self,js,name,**kwargs): |
||
55 | assert(name) |
||
56 | return js |
||
57 | |||
58 | class FixedIntegerArrayType(struct.Struct): |
||
59 | class Instance: |
||
60 | def __init__(self,array_descriptor,value): |
||
61 | element_descriptor = array_descriptor.element_descriptor |
||
62 | self.value = [element_descriptor.Instance(element_descriptor,v) for v in value] |
||
63 | def stwritephys(self,fp): |
||
64 | for value in self.value: |
||
65 | value.stwritephys(fp=fp) |
||
66 | def __init__(self,element_descriptor,element_count): |
||
67 | self.element_descriptor = element_descriptor |
||
68 | struct.Struct.__init__(self,b'<' + (element_descriptor.format[1:] * element_count)) |
||
69 | self.Struct = self |
||
70 | def streadphys(self,fp,**kwargs): |
||
71 | return self.Instance(self,SerializeableSingleType.streadphys(self,fp)) |
||
72 | def streadjs(self,js,name,**kwargs): |
||
73 | return self.Instance(self,SerializeableSingleType.streadjs(self,js,name)) |
||
74 | |||
75 | class IntegerType(struct.Struct): |
||
76 | class Instance(SerializeableSingleType.Instance): |
||
77 | def __init__(self,descriptor,value): |
||
78 | SerializeableSingleType.Instance.__init__(self,descriptor,int(value)) |
||
79 | def __init__(self,format_string): |
||
80 | struct.Struct.__init__(self,format_string) |
||
81 | self.Struct = self |
||
82 | def __mul__(self,length): |
||
83 | return type('array%u.%s' % (length, self.__class__.__name__), (FixedIntegerArrayType,), {})(self, length) |
||
84 | def streadphys(self,fp,**kwargs): |
||
85 | value = SerializeableSingleType.streadphys(self,fp) |
||
86 | assert(len(value) == 1) |
||
87 | return self.Instance(self,value[0]) |
||
88 | def streadjs(self,js,name,**kwargs): |
||
89 | return self.Instance(self,SerializeableSingleType.streadjs(self,js,name)) |
||
90 | @classmethod |
||
91 | def create(cls,name,format_string): |
||
92 | return type(name, (cls,), {})(format_string) |
||
93 | |||
94 | int16 = IntegerType.create('int16', '<h') |
||
95 | int32 = IntegerType.create('int32', '<i') |
||
96 | uint8 = IntegerType.create('uint8', '<B') |
||
97 | uint32 = IntegerType.create('uint32', '<I') |
||
98 | |||
99 | class FixedStructArrayType: |
||
100 | class Instance: |
||
101 | def __init__(self,value): |
||
102 | self.value = value |
||
103 | def stwritephys(self,fp): |
||
104 | for value in self.value: |
||
105 | value.stwritephys(fp=fp) |
||
106 | def __init__(self,element_descriptor,element_count): |
||
107 | self.element_descriptor = element_descriptor |
||
108 | self.element_count = element_count |
||
109 | def streadphys(self,fp,**kwargs): |
||
110 | return self.Instance([self.element_descriptor.streadphys(fp=fp) for i in range(self.element_count)]) |
||
111 | def streadjs(self,js,**kwargs): |
||
112 | return self.Instance([self.element_descriptor.streadjs(js=js[i]) for i in range(self.element_count)]) |
||
113 | |||
114 | class SerializeableStructType: |
||
115 | class Instance: |
||
116 | def __init__(self,owner): |
||
117 | # Ordering is not required for correct operation, but it makes |
||
118 | # debugging output easier to read. |
||
119 | self.value = collections.OrderedDict() |
||
120 | self.__owner = owner |
||
121 | def stwritephys(self,fp): |
||
122 | for (field_name,field_type) in self.__owner._struct_fields_: |
||
123 | if field_name is None: |
||
124 | field_type.stwritephys(fp=fp,container=self) |
||
125 | else: |
||
126 | self.value[field_name].stwritephys(fp=fp) |
||
127 | class MissingMemberError(KeyError): |
||
128 | pass |
||
129 | @classmethod |
||
130 | def create(cls,name,fields): |
||
131 | t = type(name, (cls,), {}) |
||
132 | t._struct_fields_ = fields |
||
133 | return t() |
||
134 | def streadphys(self,fp,**kwargs): |
||
135 | result = self.Instance(self) |
||
136 | for (name,field_type) in self._struct_fields_: |
||
137 | value = field_type.streadphys(fp=fp, container=result) |
||
138 | if name: |
||
139 | result.value[name] = value |
||
140 | return result |
||
141 | def streadjs(self,js,**kwargs): |
||
142 | result = self.Instance(self) |
||
143 | for (field_name,field_type) in self._struct_fields_: |
||
144 | field_value = js |
||
145 | if field_name: |
||
146 | if not field_name in js: |
||
147 | raise self.MissingMemberError(field_name) |
||
148 | field_value = js[field_name] |
||
149 | value = field_type.streadjs(js=field_value, container=result, name=field_name) |
||
150 | if field_name: |
||
151 | result.value[field_name] = value |
||
152 | return result |
||
153 | def __mul__(self,length): |
||
154 | return type('array%u.%s' % (length, self.__class__.__name__), (FixedStructArrayType,), {})(self, length) |
||
155 | |||
156 | class MagicNumberType(IntegerType): |
||
157 | class Instance(IntegerType.Instance): |
||
158 | def __init__(self,descriptor,value): |
||
159 | IntegerType.Instance.__init__(self,descriptor,value) |
||
160 | descriptor.check_magic(self) |
||
161 | class MagicNumberError(ValueError): |
||
162 | def __init__(self,value,expected_number): |
||
163 | ValueError.__init__(self, "Invalid magic number: wanted %.8x, got %.8x" % (expected_number, value)) |
||
164 | def __init__(self,expected_number,format_string = uint32.format): |
||
165 | IntegerType.__init__(self, format_string) |
||
166 | self.expected_number = expected_number |
||
167 | def check_magic(self,value): |
||
168 | if value.value != self.expected_number: |
||
169 | raise self.MagicNumberError(value.value, self.expected_number) |
||
170 | |||
171 | class VariadicArray: |
||
172 | class MismatchLengthError(ValueError): |
||
173 | pass |
||
174 | class MinimumLengthError(ValueError): |
||
175 | pass |
||
176 | class MaximumLengthError(ValueError): |
||
177 | pass |
||
178 | def __init__(self,field,minimum=0,maximum=None,format_struct=uint32): |
||
179 | self.field = field |
||
180 | self.minimum = minimum |
||
181 | self.maximum = maximum |
||
182 | self.Struct = format_struct |
||
183 | def streadphys(self,fp,container,**kwargs): |
||
184 | element_count = SerializeableSingleType.streadphys(self.Struct, fp)[0] |
||
185 | if element_count < self.minimum: |
||
186 | raise MinimumLengthError("Invalid element count, must be at least %u, got %.8x; fp=%.8x" % (self.minimum, element_count, fp.tell())) |
||
187 | if self.maximum is not None and element_count >= self.maximum: |
||
188 | raise MaximumLengthError("Invalid element count, must be less than %u, got %.8x; fp=%.8x" % (self.maximum, element_count, fp.tell())) |
||
189 | for (field_name,field_type) in self.field: |
||
190 | streadphys = field_type.streadphys |
||
191 | container.value[field_name] = [streadphys(fp=fp,container=container,i=i) for i in range(element_count)] |
||
192 | def streadjs(self,js,container,name,**kwargs): |
||
193 | assert(name is None) |
||
194 | element_count = None |
||
195 | for (field_name,field_type) in self.field: |
||
196 | value = js[field_name] |
||
197 | lvalue = len(value) |
||
198 | if element_count is not None and element_count != lvalue: |
||
199 | raise MismatchLengthError("Invalid element count, must be %u to match prior list, but got %u" % (element_count, lvalue)) |
||
200 | element_count = lvalue |
||
201 | streadjs = field_type.streadjs |
||
202 | container.value[field_name] = [streadjs(js=value[i],container=container,name=field_name) for i in range(element_count)] |
||
203 | if element_count is None: |
||
204 | if self.minimum > 0: |
||
205 | raise MinimumLengthError("Invalid element count, must be at least %u, got None" % (self.minimum)) |
||
206 | else: |
||
207 | if element_count < self.minimum: |
||
208 | raise MinimumLengthError("Invalid element count, must be at least %u, got %.8x" % (self.minimum, element_count)) |
||
209 | if self.maximum is not None and element_count >= self.maximum: |
||
210 | raise MaximumLengthError("Invalid element count, must be less than %u, got %.8x" % (self.maximum, element_count)) |
||
211 | def stwritephys(self,fp,container): |
||
212 | element_count = None |
||
213 | for (field_name,field_type) in self.field: |
||
214 | value = container.value[field_name] |
||
215 | lvalue = len(value) |
||
216 | if element_count is not None and element_count != lvalue: |
||
217 | raise MismatchLengthError("Invalid element count, must be %u to match prior list, but got %u" % (element_count, lvalue)) |
||
218 | element_count = lvalue |
||
219 | if element_count is None: |
||
220 | element_count = 0 |
||
221 | fp.write(self.Struct.pack(element_count)) |
||
222 | for (field_name,field_type) in self.field: |
||
223 | for value in container.value[field_name]: |
||
224 | value.stwritephys(fp=fp) |
||
225 | |||
226 | class ByteBlobType: |
||
227 | class Instance: |
||
228 | def __init__(self,value): |
||
229 | self.__value = value |
||
230 | def stwritephys(self,fp): |
||
231 | fp.write(self.__value) |
||
232 | @property |
||
233 | def value(self): |
||
234 | return tuple(self.__value) |
||
235 | def streadjs(self,js,**kwargs): |
||
236 | return self.Instance(bytes(js)) |
||
237 | |||
238 | class D2PolygonData(ByteBlobType): |
||
239 | def streadphys(self,fp,i,container,**kwargs): |
||
240 | size = container.value['polygon_models'][i].value[D2PolygonModel.model_data_size].value |
||
241 | return self.Instance(fp.read(size)) |
||
242 | |||
243 | class TailPaddingType(ByteBlobType): |
||
244 | def streadphys(self,fp,**kwargs): |
||
245 | return self.Instance(fp.read()) |
||
246 | |||
247 | class PHYSFSX: |
||
248 | Byte = uint8 |
||
249 | Short = FixAng = int16 |
||
250 | Int = Fix = int32 |
||
251 | Vector = SerializeableStructType.create('PHYSFSX.Vector', ( |
||
252 | ('x', int32), |
||
253 | ('y', int32), |
||
254 | ('z', int32), |
||
255 | )) |
||
256 | AngleVec = SerializeableStructType.create('PHYSFSX.AngleVec', ( |
||
257 | ('p', int16), |
||
258 | ('b', int16), |
||
259 | ('h', int16), |
||
260 | )) |
||
261 | |||
262 | BitmapIndex = SerializeableStructType.create('BitmapIndex', ( |
||
263 | ('index', PHYSFSX.Short), |
||
264 | )) |
||
265 | |||
266 | WeaponInfo = SerializeableStructType.create('WeaponInfo', ( |
||
267 | ('render_type', PHYSFSX.Byte), |
||
268 | ('persistent', PHYSFSX.Byte), |
||
269 | ('model_num', PHYSFSX.Short), |
||
270 | ('model_num_inner', PHYSFSX.Short), |
||
271 | ('flash_vclip', PHYSFSX.Byte), |
||
272 | ('robot_hit_vclip', PHYSFSX.Byte), |
||
273 | ('flash_sound', PHYSFSX.Short), |
||
274 | ('wall_hit_vclip', PHYSFSX.Byte), |
||
275 | ('fire_count', PHYSFSX.Byte), |
||
276 | ('robot_hit_sound', PHYSFSX.Short), |
||
277 | ('ammo_usage', PHYSFSX.Byte), |
||
278 | ('weapon_vclip', PHYSFSX.Byte), |
||
279 | ('wall_hit_sound', PHYSFSX.Short), |
||
280 | ('destroyable', PHYSFSX.Byte), |
||
281 | ('matter', PHYSFSX.Byte), |
||
282 | ('bounce', PHYSFSX.Byte), |
||
283 | ('homing_flag', PHYSFSX.Byte), |
||
284 | ('speedvar', PHYSFSX.Byte), |
||
285 | ('flags', PHYSFSX.Byte), |
||
286 | ('flash', PHYSFSX.Byte), |
||
287 | ('afterburner_size', PHYSFSX.Byte), |
||
288 | ('children', PHYSFSX.Byte), |
||
289 | ('energy_usage', PHYSFSX.Fix), |
||
290 | ('fire_wait', PHYSFSX.Fix), |
||
291 | ('multi_damage_scale', PHYSFSX.Fix), |
||
292 | ('bitmap', BitmapIndex), |
||
293 | ('blob_size', PHYSFSX.Fix), |
||
294 | ('flash_size', PHYSFSX.Fix), |
||
295 | ('impact_size', PHYSFSX.Fix), |
||
296 | ('strength', PHYSFSX.Fix * NDL), |
||
297 | ('speed', PHYSFSX.Fix * NDL), |
||
298 | ('mass', PHYSFSX.Fix), |
||
299 | ('drag', PHYSFSX.Fix), |
||
300 | ('thrust', PHYSFSX.Fix), |
||
301 | ('po_len_to_width_ratio', PHYSFSX.Fix), |
||
302 | ('light', PHYSFSX.Fix), |
||
303 | ('lifetime', PHYSFSX.Fix), |
||
304 | ('damage_radius', PHYSFSX.Fix), |
||
305 | ('picture', BitmapIndex), |
||
306 | ('hires_picture', BitmapIndex), |
||
307 | )) |
||
308 | |||
309 | Joint = SerializeableStructType.create('Joint', ( |
||
310 | ('n_joints', PHYSFSX.Short), |
||
311 | ('offset', PHYSFSX.Short), |
||
312 | )) |
||
313 | |||
314 | AnimationState = SerializeableStructType.create('AnimationState', ( |
||
315 | ('joints', Joint * N_ANIM_STATES), |
||
316 | )) |
||
317 | |||
318 | D2RobotInfo = SerializeableStructType.create('D2RobotInfo', ( |
||
319 | ('model_num', PHYSFSX.Int), |
||
320 | ('gun_points', PHYSFSX.Vector * MAX_GUNS), |
||
321 | ('gun_submodels', PHYSFSX.Byte * MAX_GUNS), |
||
322 | ('exp1_vclip_num', PHYSFSX.Short), |
||
323 | ('exp1_sound_num', PHYSFSX.Short), |
||
324 | ('exp2_vclip_num', PHYSFSX.Short), |
||
325 | ('exp2_sound_num', PHYSFSX.Short), |
||
326 | ('weapon_type', PHYSFSX.Byte), |
||
327 | ('weapon_type2', PHYSFSX.Byte), |
||
328 | ('n_guns', PHYSFSX.Byte), |
||
329 | ('contains_id', PHYSFSX.Byte), |
||
330 | ('contains_count', PHYSFSX.Byte), |
||
331 | ('contains_prob', PHYSFSX.Byte), |
||
332 | ('contains_type', PHYSFSX.Byte), |
||
333 | ('kamikaze', PHYSFSX.Byte), |
||
334 | ('score_value', PHYSFSX.Short), |
||
335 | ('badass', PHYSFSX.Byte), |
||
336 | ('energy_drain', PHYSFSX.Byte), |
||
337 | ('lighting', PHYSFSX.Fix), |
||
338 | ('strength', PHYSFSX.Fix), |
||
339 | ('mass', PHYSFSX.Fix), |
||
340 | ('drag', PHYSFSX.Fix), |
||
341 | ('field_of_view', PHYSFSX.Fix * NDL), |
||
342 | ('firing_wait', PHYSFSX.Fix * NDL), |
||
343 | ('firing_wait2', PHYSFSX.Fix * NDL), |
||
344 | ('turn_time', PHYSFSX.Fix * NDL), |
||
345 | ('max_speed', PHYSFSX.Fix * NDL), |
||
346 | ('circle_distance', PHYSFSX.Fix * NDL), |
||
347 | ('rapidfire_count', PHYSFSX.Byte * NDL), |
||
348 | ('evade_speed', PHYSFSX.Byte * NDL), |
||
349 | ('cloak_type', PHYSFSX.Byte), |
||
350 | ('attack_type', PHYSFSX.Byte), |
||
351 | ('see_sound', PHYSFSX.Byte), |
||
352 | ('attack_sound', PHYSFSX.Byte), |
||
353 | ('claw_sound', PHYSFSX.Byte), |
||
354 | ('taunt_sound', PHYSFSX.Byte), |
||
355 | ('boss_flag', PHYSFSX.Byte), |
||
356 | ('companion', PHYSFSX.Byte), |
||
357 | ('smart_blobs', PHYSFSX.Byte), |
||
358 | ('energy_blobs', PHYSFSX.Byte), |
||
359 | ('thief', PHYSFSX.Byte), |
||
360 | ('pursuit', PHYSFSX.Byte), |
||
361 | ('lightcast', PHYSFSX.Byte), |
||
362 | ('death_roll', PHYSFSX.Byte), |
||
363 | ('flags', PHYSFSX.Byte), |
||
364 | ('pad', PHYSFSX.Byte * 3), |
||
365 | ('deathroll_sound', PHYSFSX.Byte), |
||
366 | ('glow', PHYSFSX.Byte), |
||
367 | ('behavior', PHYSFSX.Byte), |
||
368 | ('aim', PHYSFSX.Byte), |
||
369 | ('anim_states', AnimationState * (MAX_GUNS + 1)), |
||
370 | ('always_0xabcd', MagicNumberType(0xabcd)), |
||
371 | )) |
||
372 | |||
373 | D2RobotJoints = SerializeableStructType.create('D2RobotJoints', ( |
||
374 | ('jointnum', PHYSFSX.Short), |
||
375 | ('angles', PHYSFSX.AngleVec), |
||
376 | )) |
||
377 | |||
378 | D2PolygonModel = SerializeableStructType.create('D2PolygonModel', ( |
||
379 | ('n_models', PHYSFSX.Int), |
||
380 | ('model_data_size', PHYSFSX.Int), |
||
381 | ('model_data_ptr', PHYSFSX.Int), |
||
382 | ('submodel_ptrs', PHYSFSX.Int * MAX_SUBMODELS), |
||
383 | ('submodel_offsets', PHYSFSX.Vector * MAX_SUBMODELS), |
||
384 | ('submodel_norms', PHYSFSX.Vector * MAX_SUBMODELS), |
||
385 | ('submodel_pnts', PHYSFSX.Vector * MAX_SUBMODELS), |
||
386 | ('submodel_rads', PHYSFSX.Fix * MAX_SUBMODELS), |
||
387 | ('submodel_parents', uint8 * MAX_SUBMODELS), |
||
388 | ('submodel_mins', PHYSFSX.Vector * MAX_SUBMODELS), |
||
389 | ('submodel_maxs', PHYSFSX.Vector * MAX_SUBMODELS), |
||
390 | ('mins', PHYSFSX.Vector), |
||
391 | ('maxs', PHYSFSX.Vector), |
||
392 | ('rad', PHYSFSX.Fix), |
||
393 | ('n_textures', PHYSFSX.Byte), |
||
394 | ('first_texture', PHYSFSX.Short), |
||
395 | ('simpler_model', PHYSFSX.Byte), |
||
396 | )) |
||
397 | |||
398 | D2PolygonModel.model_data_size = 'model_data_size' |
||
399 | |||
400 | HAM1 = SerializeableStructType.create('HAM1', ( |
||
401 | ('signature', MagicNumberType(1481130317)), # 'XHAM' |
||
402 | ('version', uint32), |
||
403 | (None, VariadicArray((('weapon_info', WeaponInfo),), 0, MAX_WEAPON_TYPES - N_D2_WEAPON_TYPES)), |
||
404 | (None, VariadicArray((('robot_info', D2RobotInfo),), 0, MAX_ROBOT_TYPES - N_D2_ROBOT_TYPES)), |
||
405 | (None, VariadicArray((('robot_joints', D2RobotJoints),), 0, MAX_ROBOT_JOINTS - N_D2_ROBOT_JOINTS)), |
||
406 | (None, VariadicArray(( |
||
407 | ('polygon_models', D2PolygonModel), |
||
408 | ('polymodel_data', D2PolygonData()), |
||
409 | ('dying_modelnum', int32), |
||
410 | ('dead_modelnum', int32), |
||
411 | ), 0, MAX_POLYGON_MODELS - N_D2_POLYGON_MODELS)), |
||
412 | (None, VariadicArray(( |
||
413 | ('obj_bitmaps', BitmapIndex), |
||
414 | ), 0, MAX_OBJ_BITMAPS - N_D2_OBJBITMAPS)), |
||
415 | (None, VariadicArray(( |
||
416 | ('obj_bitmap_ptrs', int16), |
||
417 | ), 0, MAX_OBJ_BITMAPS - N_D2_OBJBITMAPPTRS)), |
||
418 | ('tail_padding', TailPaddingType()) |
||
419 | )) |
||
420 | |||
421 | class D2HXM1(SerializeableStructType): |
||
422 | RobotTypeIndex = uint32 |
||
423 | RobotJointIndex = uint32 |
||
424 | PolygonIndex = uint32 |
||
425 | ObjBitmapIndex = uint32 |
||
426 | class D2HXMPolygonDataType(ByteBlobType): |
||
427 | polygon_model = 'polygon_model' |
||
428 | def streadphys(self,fp,container): |
||
429 | size = container.value[self.polygon_model].value[D2PolygonModel.model_data_size].value |
||
430 | return self.Instance(fp.read(size)) |
||
431 | _struct_fields_ = ( |
||
432 | ('signature', MagicNumberType(0x21584d48)), # '!MXH' |
||
433 | ('version', uint32), |
||
434 | (None, VariadicArray(( |
||
435 | ('replace_robot', SerializeableStructType.create('ReplaceRobot', ( |
||
436 | ('index', RobotTypeIndex), |
||
437 | ('robot_info', D2RobotInfo), |
||
438 | ))), |
||
439 | ))), |
||
440 | (None, VariadicArray(( |
||
441 | ('replace_joint', SerializeableStructType.create('ReplaceJoint', ( |
||
442 | ('index', RobotJointIndex), |
||
443 | ('robot_joints', D2RobotJoints), |
||
444 | ))), |
||
445 | ))), |
||
446 | (None, VariadicArray(( |
||
447 | ('replace_polygon', SerializeableStructType.create('ReplacePolygon', ( |
||
448 | ('index', PolygonIndex), |
||
449 | (D2HXMPolygonDataType.polygon_model, D2PolygonModel), |
||
450 | ('polymodel_data', D2HXMPolygonDataType()), |
||
451 | ('dying_modelnum', int32), |
||
452 | ('dead_modelnum', int32), |
||
453 | ))), |
||
454 | ))), |
||
455 | (None, VariadicArray(( |
||
456 | ('replace_objbitmap', SerializeableStructType.create('ReplaceObjBitmap', ( |
||
457 | ('index', ObjBitmapIndex), |
||
458 | ('obj_bitmap', BitmapIndex), |
||
459 | ))), |
||
460 | ))), |
||
461 | (None, VariadicArray(( |
||
462 | ('replace_objbitmapptr', SerializeableStructType.create('', ( |
||
463 | ('index', ObjBitmapIndex), |
||
464 | ('obj_bitmap_ptr', int16), |
||
465 | ))), |
||
466 | ))), |
||
467 | ('tail_padding', TailPaddingType()) |
||
468 | ) |
||
469 | |||
470 | HXM1 = D2HXM1() |
||
471 | |||
472 | class JSONEncoder(json.JSONEncoder): |
||
473 | def default(self,o): |
||
474 | try: |
||
475 | return o.value |
||
476 | except AttributeError: |
||
477 | pass |
||
478 | return json.JSONEncoder.default(self, o) |
||
479 | |||
480 | class FileFormat: |
||
481 | ExtensionTypeMap = { |
||
482 | 'ham': HAM1, |
||
483 | 'hxm': HXM1, |
||
484 | } |
||
485 | class UnknownFormatError(KeyError): |
||
486 | pass |
||
487 | class InvalidFormatError(KeyError): |
||
488 | pass |
||
489 | @classmethod |
||
490 | def guess(cls,infile,outfile,arg): |
||
491 | for filename in (infile, outfile): |
||
492 | (b, ext) = os.path.splitext(filename) |
||
493 | ext = ext.lower() |
||
494 | if ext == '.json': |
||
495 | ext = os.path.splitext(b)[1].lower() |
||
496 | result = cls.ExtensionTypeMap.get(ext.lstrip('.')) |
||
497 | if result: |
||
498 | return result |
||
499 | raise cls.UnknownFormatError("Unknown file format specify format using --%s" % arg) |
||
500 | @classmethod |
||
501 | def get(cls,infile,fmt,outfile,arg): |
||
502 | inJSON = (os.path.splitext(infile)[1].lower().lstrip('.') == 'json') |
||
503 | outJSON = (os.path.splitext(outfile)[1].lower().lstrip('.') == 'json') |
||
504 | ecls = cls.ExtensionTypeMap[fmt] if fmt else FileFormat.guess(infile, outfile, arg) |
||
505 | encodeToJSON = outJSON if inJSON ^ outJSON else None |
||
506 | return (ecls, encodeToJSON) |
||
507 | |||
508 | def main(): |
||
509 | class IndentCheck: |
||
510 | choices = ['tab', 'space', 'none'] |
||
511 | class Iterator: |
||
512 | def __init__(self,choices): |
||
513 | self.index = 0 |
||
514 | self.choices = choices |
||
515 | def __iter__(self): |
||
516 | return self |
||
517 | def __next__(self): |
||
518 | i = self.index |
||
519 | self.index = i + 1 |
||
520 | if i < len(self.choices): |
||
521 | return self.choices[i] |
||
522 | raise StopIteration |
||
523 | def __contains__(self,value): |
||
524 | return value.strip() == '' or value in self.choices |
||
525 | def __iter__(self): |
||
526 | return self.Iterator(self.choices) |
||
527 | import argparse |
||
528 | parser = argparse.ArgumentParser(description='convert Descent data files to/from binary/JSON') |
||
529 | parser.add_argument('--format', choices=FileFormat.ExtensionTypeMap.keys(), help='binary file format to use', metavar='FORMAT') |
||
530 | parser.add_argument('--indent', choices=IndentCheck(), help='how to indent JSON output', default='\t') |
||
531 | group = parser.add_mutually_exclusive_group() |
||
532 | group.add_argument('--encode', action='store_true', default=None, help='treat input as binary and output as JSON') |
||
533 | group.add_argument('--decode', dest='encode', action='store_false', help='treat input as JSON and output as binary') |
||
534 | parser.add_argument('input', help='file to read', metavar='input-file') |
||
535 | parser.add_argument('output', help='file to write', metavar='output-file') |
||
536 | args = parser.parse_args() |
||
537 | (cls, encodeToJSON) = FileFormat.get(args.input,args.format,args.output,'format') |
||
538 | if args.encode is not None: |
||
539 | encodeToJSON = args.encode |
||
540 | if encodeToJSON is None: |
||
541 | parser.error("Neither input nor output ends in json. Use --encode or --decode to specify whether to produce JSON or consume JSON.") |
||
542 | if encodeToJSON: |
||
543 | with open(args.input, 'rb') as f: |
||
544 | i = cls.streadphys(f) |
||
545 | indent = args.indent |
||
546 | if indent == 'space': |
||
547 | indent = ' ' |
||
548 | elif indent == 'tab': |
||
549 | indent = '\t' |
||
550 | elif indent == 'none': |
||
551 | indent = '' |
||
552 | with open(args.output, 'wt') as f: |
||
553 | json.dump(i, f, cls=JSONEncoder, indent=indent) |
||
554 | else: |
||
555 | with open(args.input, 'rt') as f: |
||
556 | i = cls.streadjs(js=json.load(f, object_pairs_hook=collections.OrderedDict)) |
||
557 | with open(args.output, 'wb') as f: |
||
558 | i.stwritephys(f) |
||
559 | |||
560 | if __name__ == '__main__': |
||
561 | main() |