def unpack(self, buffer: io.BufferedIOBase, bounding_box_has_unknown: bool = None): data = self.STRUCT.unpack(buffer) mesh_offset = buffer.tell() bounding_box_offset = data.pop("__bounding_box_offset") if bounding_box_offset != 0: buffer.seek(bounding_box_offset) self.bounding_box = BoundingBoxWithUnknown(buffer) if bounding_box_has_unknown else BoundingBox(buffer) else: self.bounding_box = None buffer.seek(data.pop("__bone_offset")) bone_count = data.pop("__bone_count") self.bone_indices = list(unpack_from_buffer(buffer, f"<{bone_count}i",)) buffer.seek(data.pop("__face_set_offset")) face_set_count = data.pop("__face_set_count") self._face_set_indices = list(unpack_from_buffer(buffer, f"<{face_set_count}i")) buffer.seek(data.pop("__vertex_buffer_offset")) vertex_count = data.pop("__vertex_buffer_count") self._vertex_buffer_indices = list(unpack_from_buffer(buffer, f"<{vertex_count}i")) buffer.seek(mesh_offset) self.set(**data)
def _check_use_struct_64(info_buffer, goal_count): if goal_count == 0: raise LuaError( "Cannot detect `LuaInfo` version if no goals are present.") elif goal_count >= 2: return unpack_from_buffer(info_buffer, "i", offset=0x24)[0] == 0 else: # Hacky check if there's only one goal. if unpack_from_buffer(info_buffer, "i", offset=0x18)[0] == 0x28: return True if unpack_from_buffer(info_buffer, "i", offset=0x14)[0] == 0x20: return False raise ValueError( "Found unexpected data while trying to detect `LuaInfo` version from single goal." )
def ENTRY_CLASS(cls, msb_buffer): """Detects the appropriate subclass of `MSBPart` to instantiate, and does so.""" entry_subtype_int = unpack_from_buffer(msb_buffer, "i", offset=cls.SUBTYPE_OFFSET, relative_offset=True)[0] try: entry_subtype = cls.ENTRY_SUBTYPE_ENUM(entry_subtype_int) except ValueError: raise MapError( f"Entry of type {cls.ENTRY_SUBTYPE_ENUM} has invalid subtype enum: {entry_subtype_int}" ) return cls.SUBTYPE_CLASSES[entry_subtype](msb_buffer)
class FaceSet(BinaryObject): STRUCT = BinaryStruct( ("flags", "B"), "3x", ("triangle_strip", "?"), ("cull_back_faces", "?"), ("unk_x06", "h"), ("__indices_count", "i"), ("__indices_offset", "i"), # header stops here for versions < 0x20005, which are not supported by Soulstruct ("__indices_length", "i"), # unused "4x", ("__index_size", "i"), # 0 (from header), 16, or 32 "4x", ) flags: FaceSetFlags triangle_strip: bool cull_back_faces: bool unk_x06: int indices: list[int] def unpack(self, buffer: io.BufferedIOBase, header_index_size: int = None, data_offset: int = None): data = self.STRUCT.unpack(buffer) if (index_size := data.pop("__index_size")) == 0: index_size = header_index_size if index_size == 8: raise NotImplementedError("Soulstruct cannot support edge-compressed FLVER face sets (`index_size=8`).") elif index_size in {16, 32}: face_set_offset = buffer.tell() indices_count = data.pop("__indices_count") indices_offset = data.pop("__indices_offset") buffer.seek(data_offset + indices_offset) self.indices = list(unpack_from_buffer(buffer, f"<{indices_count}{'H' if index_size == 16 else 'I'}")) buffer.seek(face_set_offset) else: raise ValueError(f"Unsupported face set index size: {index_size}") self.set(**data)
def unpack(self, buffer, **kwargs): self.byte_order = ">" if unpack_from_buffer(buffer, "B", 44)[0] == 255 else "<" version_info = unpack_from_buffer(buffer, f"{self.byte_order}bbb", 45) self.flags1 = ParamFlags1(version_info[0]) self.flags2 = ParamFlags2(version_info[1]) self.paramdef_format_version = version_info[2] header_struct = self.GET_HEADER_STRUCT(self.flags1, self.byte_order) header = header_struct.unpack(buffer) try: self.param_type = header["param_type"] except KeyError: self.param_type = read_chars_from_buffer( buffer, offset=header["param_type_offset"], encoding="utf-8") self.paramdef_data_version = header["paramdef_data_version"] self.unknown = header["unknown"] # Row data offset in header not used. (It's an unsigned short, yet doesn't limit row count to 5461.) name_data_offset = header[ "name_data_offset"] # CANNOT BE TRUSTED IN VANILLA FILES! Off by +12 bytes. # Load row pointer data. if self.flags1.LongDataOffset: row_pointers = self.ROW_STRUCT_64.unpack_count( buffer, count=header["row_count"]) else: row_pointers = self.ROW_STRUCT_32.unpack_count( buffer, count=header["row_count"]) row_data_offset = buffer.tell() # Reliable row data offset. # Row size is lazily determined. TODO: Unpack row data in sequence and associate with names separately. if len(row_pointers) == 0: return elif len(row_pointers) == 1: # NOTE: The only vanilla param in Dark Souls with one row is LEVELSYNC_PARAM_ST (Remastered only), # for which the row size is hard-coded here. Otherwise, we can trust the repacked offset from Soulstruct # (and SoulsFormats, etc.). if self.param_type == "LEVELSYNC_PARAM_ST": row_size = 220 else: row_size = name_data_offset - row_data_offset else: row_size = row_pointers[1]["data_offset"] - row_pointers[0][ "data_offset"] # Note that we no longer need to track buffer offset. name_encoding = self.get_name_encoding() for row_struct in row_pointers: buffer.seek(row_struct["data_offset"]) row_data = buffer.read(row_size) if row_struct["name_offset"] != 0: try: name = read_chars_from_buffer( buffer, offset=row_struct["name_offset"], encoding=name_encoding, reset_old_offset=False, # no need to reset ) except UnicodeDecodeError as ex: if ex.object in self.undecodable_row_names: name = read_chars_from_buffer( buffer, offset=row_struct["name_offset"], encoding=None, reset_old_offset=False, # no need to reset ) else: raise except ValueError: buffer.seek(row_struct["name_offset"]) _LOGGER.error( f"Error encountered while parsing row name string in {self.param_type}.\n" f" Header: {header}\n" f" Row Struct: {row_struct}\n" f" 30 chrs of name data: {' '.join(f'{{:02x}}'.format(x) for x in buffer.read(30))}" ) raise else: name = "" self.rows[row_struct["id"]] = ParamRow(row_data, self.paramdef, name=name)
def unpack_argb(cls, buffer: io.BufferedIOBase): alpha, red, green, blue = unpack_from_buffer(buffer, "<4f") return cls(red, green, blue, alpha)
def unpack_bgra(cls, buffer: io.BufferedIOBase): blue, green, red, alpha = unpack_from_buffer(buffer, "<4f") return cls(red, green, blue, alpha)
def unpack(self, buffer): self.gx_items = [] while (gx_id := unpack_from_buffer( buffer, "<i", offset=buffer.tell())) not in {2**31 - 1, -1}: self.gx_items.append(GXItem(buffer))
def read(self, buffer: io.BufferedIOBase, layout: BufferLayout, uv_factor: float): self.uvs = [] self.tangents = [] self.colors = [] for member in layout: not_implemented = False if member.semantic == LayoutSemantic.Position: if member.layout_type == LayoutType.Float3: self.position = Vector3(unpack_from_buffer(buffer, "<3f")) elif member.layout_type == LayoutType.Float4: self.position = Vector3(unpack_from_buffer(buffer, "<3f"))[:3] elif member.layout_type == LayoutType.EdgeCompressed: raise NotImplementedError( "Soulstruct cannot load FLVERs with edge-compressed vertex positions." ) else: not_implemented = True elif member.semantic == LayoutSemantic.BoneWeights: if member.layout_type == LayoutType.Byte4A: self.bone_weights = VertexBoneWeights( * [w / 127.0 for w in unpack_from_buffer(buffer, "<4b")]) elif member.layout_type == LayoutType.Byte4C: self.bone_weights = VertexBoneWeights( * [w / 255.0 for w in unpack_from_buffer(buffer, "<4B")]) elif member.layout_type == LayoutType.UVPair: self.bone_weights = VertexBoneWeights(*[ w / 32767.0 for w in unpack_from_buffer(buffer, "<4h") ]) elif member.layout_type == LayoutType.Short4ToFloat4A: self.bone_weights = VertexBoneWeights(*[ w / 32767.0 for w in unpack_from_buffer(buffer, "<4h") ]) else: not_implemented = True elif member.semantic == LayoutSemantic.BoneIndices: if member.layout_type == LayoutType.Byte4B: self.bone_indices = VertexBoneIndices( *unpack_from_buffer(buffer, "<4B")) elif member.layout_type == LayoutType.ShortBoneIndices: self.bone_indices = VertexBoneIndices( *unpack_from_buffer(buffer, "<4h")) elif member.layout_type == LayoutType.Byte4E: self.bone_indices = VertexBoneIndices( *unpack_from_buffer(buffer, "<4B")) else: not_implemented = True elif member.semantic == LayoutSemantic.Normal: if member.layout_type == LayoutType.Float3: self.normal = Vector3(unpack_from_buffer(buffer, "<3f")) elif member.layout_type == LayoutType.Float4: self.normal = Vector3(unpack_from_buffer(buffer, "<3f")) float_normal_w = unpack_from_buffer(buffer, "<f")[0] self.normal_w = int(float_normal_w) if self.normal_w != float_normal_w: raise ValueError( f"`normal_w` float was not a whole number.") elif member.layout_type in { LayoutType.Byte4A, LayoutType.Byte4B, LayoutType.Byte4C, LayoutType.Byte4E }: self.normal = Vector3([ (x - 127) / 127.0 for x in unpack_from_buffer(buffer, "<3B") ]) self.normal_w = unpack_from_buffer(buffer, "<B")[0] elif member.layout_type == LayoutType.Short2toFloat2: self.normal_w = unpack_from_buffer(buffer, "<B")[0] self.normal = Vector3( [x / 127.0 for x in unpack_from_buffer(buffer, "<3b")]) elif member.layout_type == LayoutType.Short4ToFloat4A: self.normal = Vector3([ x / 32767.0 for x in unpack_from_buffer(buffer, "<3h") ]) self.normal_w = unpack_from_buffer(buffer, "<h")[0] elif member.layout_type == LayoutType.Short4ToFloat4B: self.normal = Vector3([ (x - 32767) / 32767.0 for x in unpack_from_buffer(buffer, "<3H") ]) self.normal_w = unpack_from_buffer(buffer, "<h")[0] else: not_implemented = True elif member.semantic == LayoutSemantic.UV: if member.layout_type == LayoutType.Float2: self.uvs.append( Vector3(*unpack_from_buffer(buffer, "<2f"), 0.0)) elif member.layout_type == LayoutType.Float3: self.uvs.append( Vector3(*unpack_from_buffer(buffer, "<3f"))) elif member.layout_type == LayoutType.Float4: self.uvs.append( Vector3(*unpack_from_buffer(buffer, "<2f"), 0.0)) self.uvs.append( Vector3(*unpack_from_buffer(buffer, "<2f"), 0.0)) elif member.layout_type in { LayoutType.Byte4A, LayoutType.Byte4B, LayoutType.Short2toFloat2, LayoutType.Byte4C, LayoutType.UV }: self.uvs.append( Vector3(*unpack_from_buffer(buffer, "<2h"), 0) / uv_factor) elif member.layout_type == LayoutType.UVPair: self.uvs.append( Vector3(*unpack_from_buffer(buffer, "<2h"), 0) / uv_factor) self.uvs.append( Vector3(*unpack_from_buffer(buffer, "<2h"), 0) / uv_factor) elif member.layout_type == LayoutType.Short4ToFloat4B: self.uvs.append( Vector3(*unpack_from_buffer(buffer, "<3h")) / uv_factor) if unpack_from_buffer(buffer, "<h") != 0: raise ValueError( "Expected null byte after reading UV | Short4ToFloat4B vertex member." ) else: not_implemented = True elif member.semantic == LayoutSemantic.Tangent: if member.layout_type == LayoutType.Float4: self.tangents.append( Vector4(*unpack_from_buffer(buffer, "<4f"))) elif member.layout_type in { LayoutType.Byte4A, LayoutType.Byte4B, LayoutType.Byte4C, LayoutType.Short4ToFloat4A, LayoutType.Byte4E, }: tangent = Vector4([ (x - 127) / 127.0 for x in unpack_from_buffer(buffer, "<4B") ]) self.tangents.append(tangent) else: not_implemented = True elif member.semantic == LayoutSemantic.Bitangent: if member.layout_type in { LayoutType.Byte4A, LayoutType.Byte4B, LayoutType.Byte4C, LayoutType.Byte4E }: self.bitangent = Vector4([ (x - 127) / 127.0 for x in unpack_from_buffer(buffer, "<4B") ]) else: not_implemented = True elif member.semantic == LayoutSemantic.VertexColor: if member.layout_type == LayoutType.Float4: self.colors.append( Color(*unpack_from_buffer(buffer, "<4f"))) elif member.layout_type in { LayoutType.Byte4A, LayoutType.Byte4C }: self.colors.append( Color(*[ b / 255.0 for b in unpack_from_buffer(buffer, "<4B") ])) else: not_implemented = True else: not_implemented = True if not_implemented: raise NotImplementedError( f"Unsupported vertex member semantic/type combination: " f"{member.semantic.name} | {member.layout_type.name}")