Subversion Repositories Games.Descent

Rev

Blame | Last modification | View Log | Download | RSS feed

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