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