Subversion Repositories Games.Descent

Rev

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()