def pack_type_data(self): navmesh_groups = _enabled_flags_to_flag_group(self.navmesh_groups) if self.area_name_id == -1 and not self.__force_area_banner: raise InvalidFieldValueError( "'force_area_banner' must be enabled if 'area_name_id' is -1 (default)." ) signed_area_name_id = self.area_name_id * ( -1 if self.area_name_id >= 0 and self.__force_area_banner else 1) if self.__stable_footing_flag != 0: play_region_id = -self.__stable_footing_flag - 10 else: play_region_id = self.__play_region_id return BinaryStruct(*self.PART_COLLISION_STRUCT).pack( hit_filter_id=self.hit_filter_id, sound_space_type=self.sound_space_type, env_light_map_spot_index=self.env_light_map_spot_index, reflect_plane_height=self.reflect_plane_height, navmesh_groups=navmesh_groups, vagrant_entity_ids=self.vagrant_entity_ids, area_name_id=signed_area_name_id, starts_disabled=self.starts_disabled, attached_bonfire=self.attached_bonfire, play_region_id=play_region_id, lock_cam_param_id_1=self.lock_cam_param_id_1, lock_cam_param_id_2=self.lock_cam_param_id_2, )
def unpack_type_data(self, msb_buffer): data = BinaryStruct(*self.PART_OBJECT_STRUCT).unpack(msb_buffer) self._collision_index = data.collision_index self.unk_x08_x0c = data.unk_x08_x0c self.object_pose = data.object_pose self.unk_x0e_x10 = data.unk_x0e_x10 self.unk_x10_x14 = data.unk_x10_x14
def unpack_type_data(self, msb_buffer): data = BinaryStruct(*self.EVENT_OBJ_ACT_STRUCT).unpack(msb_buffer) self.obj_act_entity_id = data.obj_act_entity_id self._obj_act_part_index = data.obj_act_part_index self.obj_act_param_id = data.obj_act_param_id self.unk_x0a_x0c = data.unk_x0a_x0c self.obj_act_flag = data.obj_act_flag
def pack_type_data(self): return BinaryStruct(*self.PART_OBJECT_STRUCT).pack( collision_index=self._collision_index, unk_x08_x0c=self.unk_x08_x0c, object_pose=self.object_pose, unk_x0e_x10=self.unk_x0e_x10, unk_x10_x14=self.unk_x10_x14, )
def unpack_type_data(self, msb_buffer): data = BinaryStruct(*self.EVENT_ENVIRONMENT_STRUCT).unpack(msb_buffer) self.unk_x00_x04 = data.unk_x00_x04 self.unk_x04_x08 = data.unk_x04_x08 self.unk_x08_x0c = data.unk_x08_x0c self.unk_x0c_x10 = data.unk_x0c_x10 self.unk_x10_x14 = data.unk_x10_x14 self.unk_x14_x18 = data.unk_x14_x18
def pack_type_data(self): return BinaryStruct(*self.EVENT_OBJ_ACT_STRUCT).pack( obj_act_entity_id=self.obj_act_entity_id, obj_act_part_index=self._obj_act_part_index, obj_act_param_id=self.obj_act_param_id, unk_x0a_x0c=self.unk_x0a_x0c, obj_act_flag=self.obj_act_flag, )
def pack_type_data(self): return BinaryStruct(*self.EVENT_ENVIRONMENT_STRUCT).pack( unk_x00_x04=self.unk_x00_x04, unk_x04_x08=self.unk_x04_x08, unk_x08_x0c=self.unk_x08_x0c, unk_x0c_x10=self.unk_x0c_x10, unk_x10_x14=self.unk_x10_x14, unk_x14_x18=self.unk_x14_x18, )
def unpack_type_data(self, msb_buffer): data = BinaryStruct(*self.EVENT_TREASURE_STRUCT).unpack(msb_buffer) self._treasure_part_index = data.treasure_part_index self.item_lot_1 = data.item_lot_1 self.item_lot_2 = data.item_lot_2 self.item_lot_3 = data.item_lot_3 self.item_lot_4 = data.item_lot_4 self.item_lot_5 = data.item_lot_5 self.in_chest = data.in_chest self.starts_disabled = data.starts_disabled
def unpack_type_data(self, msb_buffer): data = BinaryStruct(*self.PART_CHARACTER_STRUCT).unpack(msb_buffer) self.think_param_id = data.think_param_id self.npc_param_id = data.npc_param_id self.talk_id = data.talk_id self.unk_x14_x18 = data.unk_x14_x18 self.chara_init_id = data.chara_init_id self._collision_index = data.collision_index self._patrol_point_indices = data.patrol_point_indices self.default_animation = data.default_animation self.unk_x3c_x40 = data.unk_x3c_x40
def unpack_type_data(self, msb_buffer): data = BinaryStruct(*self.EVENT_SPAWNER_STRUCT).unpack(msb_buffer) self.max_count = data.max_count self.limit_count = data.limit_count self.min_spawner_count = data.min_spawner_count self.max_spawner_count = data.max_spawner_count self.min_interval = data.min_interval self.max_interval = data.max_interval self.initial_spawn_count = data.initial_spawn_count self._spawn_region_indices = data.spawn_region_indices self._spawn_part_indices = data.spawn_part_indices
def pack_type_data(self): return BinaryStruct(*self.EVENT_TREASURE_STRUCT).pack( treasure_part_index=self._treasure_part_index, item_lot_1=self.item_lot_1, item_lot_2=self.item_lot_2, item_lot_3=self.item_lot_3, item_lot_4=self.item_lot_4, item_lot_5=self.item_lot_5, in_chest=self.in_chest, starts_disabled=self.starts_disabled, )
def pack_type_data(self): return BinaryStruct(*self.EVENT_SPAWNER_STRUCT).pack( max_count=self.max_count, limit_count=self.limit_count, min_spawner_count=self.min_spawner_count, max_spawner_count=self.max_spawner_count, min_interval=self.min_interval, max_interval=self.max_interval, initial_spawn_count=self.initial_spawn_count, spawn_region_indices=self._spawn_region_indices, spawn_part_indices=self._spawn_part_indices, )
def pack_type_data(self): return BinaryStruct(*self.PART_CHARACTER_STRUCT).pack( think_param_id=self.think_param_id, npc_param_id=self.npc_param_id, talk_id=self.talk_id, unk_x14_x18=self.unk_x14_x18, chara_init_id=self.chara_init_id, collision_index=self._collision_index, patrol_point_indices=self._patrol_point_indices, default_animation=self.default_animation, unk_x3c_x40=self.unk_x3c_x40, )
def unpack_type_data(self, msb_buffer): data = BinaryStruct(*self.EVENT_WIND_STRUCT).unpack(msb_buffer) self.unk_x00_x04 = data.unk_x00_x04 self.unk_x04_x08 = data.unk_x04_x08 self.unk_x08_x0c = data.unk_x08_x0c self.unk_x0c_x10 = data.unk_x0c_x10 self.unk_x10_x14 = data.unk_x10_x14 self.unk_x14_x18 = data.unk_x14_x18 self.unk_x18_x1c = data.unk_x18_x1c self.unk_x1c_x20 = data.unk_x1c_x20 self.unk_x20_x24 = data.unk_x20_x24 self.unk_x24_x28 = data.unk_x24_x28 self.unk_x28_x2c = data.unk_x28_x2c self.unk_x2c_x30 = data.unk_x2c_x30 self.unk_x30_x34 = data.unk_x30_x34 self.unk_x34_x38 = data.unk_x34_x38 self.unk_x38_x3c = data.unk_x38_x3c self.unk_x3c_x40 = data.unk_x3c_x40
def pack_type_data(self): return BinaryStruct(*self.EVENT_WIND_STRUCT).pack( unk_x00_x04=self.unk_x00_x04, unk_x04_x08=self.unk_x04_x08, unk_x08_x0c=self.unk_x08_x0c, unk_x0c_x10=self.unk_x0c_x10, unk_x10_x14=self.unk_x10_x14, unk_x14_x18=self.unk_x14_x18, unk_x18_x1c=self.unk_x18_x1c, unk_x1c_x20=self.unk_x1c_x20, unk_x20_x24=self.unk_x20_x24, unk_x24_x28=self.unk_x24_x28, unk_x28_x2c=self.unk_x28_x2c, unk_x2c_x30=self.unk_x2c_x30, unk_x30_x34=self.unk_x30_x34, unk_x34_x38=self.unk_x34_x38, unk_x38_x3c=self.unk_x38_x3c, unk_x3c_x40=self.unk_x3c_x40, )
def unpack_type_data(self, msb_buffer): data = BinaryStruct(*self.PART_COLLISION_STRUCT).unpack(msb_buffer) self.hit_filter_id = data.hit_filter_id self.sound_space_type = data.sound_space_type self.env_light_map_spot_index = data.env_light_map_spot_index self.reflect_plane_height = data.reflect_plane_height self.navmesh_groups = _flag_group_to_enabled_flags(data.navmesh_groups) self.vagrant_entity_ids = data.vagrant_entity_ids self.area_name_id = abs( data.area_name_id) if data.area_name_id >= 0 else -1 self.__force_area_banner = data.area_name_id < 0 # Custom field. self.starts_disabled = data.starts_disabled self.attached_bonfire = data.attached_bonfire if data.play_region_id > -10: self.__play_region_id = data.play_region_id self.__stable_footing_flag = 0 else: self.__play_region_id = 0 self.__stable_footing_flag = -data.play_region_id - 10 self.lock_cam_param_id_1 = data.lock_cam_param_id_1 self.lock_cam_param_id_2 = data.lock_cam_param_id_2
def pack_type_data(self): return BinaryStruct(*self.BOX_STRUCT).pack( width=self.width, depth=self.depth, height=self.height, )
def unpack_type_data(self, msb_buffer): data = BinaryStruct(*self.BOX_STRUCT).unpack(msb_buffer) self.width = data.width self.depth = data.depth self.height = data.height
def pack_type_data(self): return BinaryStruct(*self.RECT_STRUCT).pack( width=self.width, depth=self.depth, )
class BaseMSBRegion(MSBEntryEntity): REGION_STRUCT = BinaryStruct( ('name_offset', 'i'), '4x', ('region_index', 'i'), ('region_type', 'i'), ('translate', '3f'), ('rotate', '3f'), # These are Euler angle rotations (and can therefore be gimbal-locked). ('unknown_offset_1', 'i'), ('unknown_offset_2', 'i'), ('type_data_offset', 'i'), ('entity_id_offset', 'i'), '4x', ) FIELD_INFO = { 'translate': ( 'Translate', True, Vector, "3D coordinates of the region's position. Note that this is the middle of the bottom face for box " "regions."), 'rotate': ( 'Rotate', True, Vector, "Euler angles for region rotation around its local X, Y, and Z axes."), 'entity_id': ( 'Entity ID', True, int, "Entity ID used to refer to the region in other game files."), } ENTRY_TYPE = None def __init__(self, msb_region_source): super().__init__() self._region_index = None self.translate = None self.rotate = None if isinstance(msb_region_source, bytes): msb_region_source = BytesIO(msb_region_source) if isinstance(msb_region_source, BufferedReader): self.unpack(msb_region_source) else: raise TypeError("'msb_model_source' must be a buffer or bytes.") def unpack(self, msb_buffer): region_offset = msb_buffer.tell() base_data = self.REGION_STRUCT.unpack(msb_buffer) self.name = read_chars_from_buffer( msb_buffer, offset=region_offset + base_data.name_offset, encoding='shift-jis') self._region_index = base_data.region_index self.translate = Vector(base_data.translate) self.rotate = Vector(base_data.rotate) self.check_null_field(msb_buffer, region_offset + base_data.unknown_offset_1) self.check_null_field(msb_buffer, region_offset + base_data.unknown_offset_2) if base_data.type_data_offset != 0: msb_buffer.seek(region_offset + base_data.type_data_offset) self.unpack_type_data(msb_buffer) msb_buffer.seek(region_offset + base_data.entity_id_offset) self.entity_id = struct.unpack('i', msb_buffer.read(4))[0] return region_offset + base_data.entity_id_offset def unpack_type_data(self, msb_buffer): raise NotImplementedError def pack(self): name_offset = self.REGION_STRUCT.size packed_name = pad_chars(self.get_name_to_pack(), encoding='shift-jis', pad_to_multiple_of=4) unknown_offset_1 = name_offset + len(packed_name) unknown_offset_2 = unknown_offset_1 + 4 packed_type_data = self.pack_type_data() if packed_type_data: type_data_offset = unknown_offset_2 + 4 entity_id_offset = type_data_offset + len(packed_type_data) else: type_data_offset = 0 entity_id_offset = unknown_offset_2 + 4 packed_base_data = self.REGION_STRUCT.pack( name_offset=name_offset, region_index=self._region_index, region_type=self.ENTRY_TYPE, translate=list(self.translate), rotate=list(self.rotate), unknown_offset_1=unknown_offset_1, unknown_offset_2=unknown_offset_2, type_data_offset=type_data_offset, entity_id_offset=entity_id_offset, ) packed_entity_id = struct.pack('i', self.entity_id) return packed_base_data + packed_name + b'\0\0\0\0' * 2 + packed_type_data + packed_entity_id def pack_type_data(self): raise NotImplementedError def set_indices(self, region_index): self._region_index = region_index @staticmethod def check_null_field(msb_buffer, offset_to_null): msb_buffer.seek(offset_to_null) zero = msb_buffer.read(4) if zero != b'\0\0\0\0': _LOGGER.warning(f"Null data entry in MSB region was not zero: {zero}.") @staticmethod def auto_region_subclass(msb_buffer): old_offset = msb_buffer.tell() msb_buffer.seek(old_offset + 12) try: region_type_int = struct.unpack('i', msb_buffer.read(4))[0] region_type = MSB_REGION_TYPE(region_type_int) except (ValueError, TypeError): region_type = None msb_buffer.seek(old_offset) return REGION_TYPE_CLASSES[region_type](msb_buffer)
def pack_type_data(self): return BinaryStruct(*self.CYLINDER_STRUCT).pack( radius=self.radius, height=self.height, )
def unpack_type_data(self, msb_buffer): data = BinaryStruct(*self.CYLINDER_STRUCT).unpack(msb_buffer) self.radius = data.radius self.height = data.height
def pack_type_data(self): return BinaryStruct(*self.SPHERE_STRUCT).pack( radius=self.radius, )
def unpack_type_data(self, msb_buffer): self.radius = BinaryStruct(*self.SPHERE_STRUCT).unpack(msb_buffer).radius
def pack_type_data(self): return BinaryStruct(*self.BOX_STRUCT).pack( width=float(self.width), depth=float(self.depth), height=float(self.height) )
def unpack_type_data(self, msb_buffer): data = BinaryStruct(*self.RECT_STRUCT).unpack(msb_buffer) self.width = data["width"] self.depth = data["depth"]
class BaseMSBRegion(MSBEntryEntityCoordinates): REGION_STRUCT = BinaryStruct( ("name_offset", "i"), "4x", ("region_index", "i"), ("region_type", "i"), ("translate", "3f"), ("rotate", "3f"), # These are Euler angle rotations (and can therefore be gimbal-locked). ("unknown_offset_1", "i"), ("unknown_offset_2", "i"), ("type_data_offset", "i"), ("entity_id_offset", "i"), "4x", ) FIELD_INFO = { "translate": ( "Translate", True, Vector3, "3D coordinates of the region's position. Note that this is the middle of the bottom face for box " "regions.", ), "rotate": ("Rotate", True, Vector3, "Euler angles for region rotation around its local X, Y, and Z axes."), "entity_id": ("Entity ID", True, int, "Entity ID used to refer to the region in other game files."), } ENTRY_SUBTYPE = None # type: MSBRegionSubtype def __init__(self, msb_region_source=None): super().__init__() self._region_index = None # Final automatic assignment done on `MSB.pack()`. if isinstance(msb_region_source, bytes): msb_region_source = BytesIO(msb_region_source) if isinstance(msb_region_source, BufferedReader): self.unpack(msb_region_source) elif msb_region_source is not None: raise TypeError("`msb_model_source` must be a buffer, `bytes`, or `None`.") def unpack(self, msb_buffer): region_offset = msb_buffer.tell() base_data = self.REGION_STRUCT.unpack(msb_buffer) self.name = read_chars_from_buffer( msb_buffer, offset=region_offset + base_data["name_offset"], encoding="shift-jis" ) self._region_index = base_data["region_index"] self.translate = Vector3(base_data["translate"]) self.rotate = Vector3(base_data["rotate"]) self.check_null_field(msb_buffer, region_offset + base_data["unknown_offset_1"]) self.check_null_field(msb_buffer, region_offset + base_data["unknown_offset_2"]) if base_data["type_data_offset"] != 0: msb_buffer.seek(region_offset + base_data["type_data_offset"]) self.unpack_type_data(msb_buffer) msb_buffer.seek(region_offset + base_data["entity_id_offset"]) self.entity_id = struct.unpack("i", msb_buffer.read(4))[0] return region_offset + base_data["entity_id_offset"] def unpack_type_data(self, msb_buffer): # TODO: Use type struct. raise NotImplementedError def pack(self): name_offset = self.REGION_STRUCT.size packed_name = pad_chars(self.get_name_to_pack(), encoding="shift-jis", pad_to_multiple_of=4) unknown_offset_1 = name_offset + len(packed_name) unknown_offset_2 = unknown_offset_1 + 4 packed_type_data = self.pack_type_data() if packed_type_data: type_data_offset = unknown_offset_2 + 4 entity_id_offset = type_data_offset + len(packed_type_data) else: type_data_offset = 0 entity_id_offset = unknown_offset_2 + 4 packed_base_data = self.REGION_STRUCT.pack( name_offset=name_offset, region_index=self._region_index, region_type=self.ENTRY_SUBTYPE, translate=list(self.translate), rotate=list(self.rotate), unknown_offset_1=unknown_offset_1, unknown_offset_2=unknown_offset_2, type_data_offset=type_data_offset, entity_id_offset=entity_id_offset, ) packed_entity_id = struct.pack("i", self.entity_id) return packed_base_data + packed_name + b"\0\0\0\0" * 2 + packed_type_data + packed_entity_id def pack_type_data(self): raise NotImplementedError def set_indices(self, region_index): self._region_index = region_index @staticmethod def check_null_field(msb_buffer, offset_to_null): msb_buffer.seek(offset_to_null) zero = msb_buffer.read(4) if zero != b"\0\0\0\0": _LOGGER.warning(f"Null data entry in MSB region was not zero: {zero}.") @staticmethod def auto_region_subclass(msb_buffer): old_offset = msb_buffer.tell() msb_buffer.seek(old_offset + 12) try: region_type = MSBRegionSubtype(struct.unpack("i", msb_buffer.read(4))[0]) except (ValueError, TypeError): raise ValueError("Could not detect region subtype from MSB data.") msb_buffer.seek(old_offset) return MSB_REGION_TYPE_CLASSES[region_type](msb_buffer)
def unpack_type_data(self, msb_buffer): self.radius = BinaryStruct(*self.CIRCLE_STRUCT).unpack(msb_buffer)["radius"]
def pack_type_data(self): return BinaryStruct(*self.PART_MAP_LOAD_TRIGGER_STRUCT).pack( collision_index=self._collision_index, map_id=self.map_id, )
def unpack_type_data(self, msb_buffer): data = BinaryStruct( *self.PART_MAP_LOAD_TRIGGER_STRUCT).unpack(msb_buffer) self.collision_name = None self._collision_index = data.collision_index self.map_id = data.map_id # TODO: Convert to a GameMap instance.