def unpack(self, reader: BinaryReader, remove_empty_entries=True): header = reader.unpack_struct(self.HEADER_STRUCT) # Groups of contiguous text string IDs are defined by ranges (first ID, last ID) to save space. ranges = reader.unpack_structs(self.RANGE_STRUCT, count=header["range_count"]) if reader.position != header["string_offsets_offset"]: _LOGGER.warning( "Range data did not end at string data offset given in FMG header." ) string_offsets = reader.unpack_structs(self.STRING_OFFSET_STRUCT, count=header["string_count"]) # Text pointer table corresponds to all the IDs (joined together) of the above ranges, in order. for string_range in ranges: i = string_range["first_index"] for string_id in range(string_range["first_id"], string_range["last_id"] + 1): if string_id in self.entries: raise ValueError( f"Malformed FMG: Entry index {string_id} appeared more than once." ) string_offset = string_offsets[i]["offset"] if string_offset == 0: if not remove_empty_entries: # Empty text string. These will trigger in-game error messages, like ?PlaceName?. # Distinct from ' ', which is intentionally blank text data (e.g. the unused area subtitles). self.entries[string_id] = "" else: string = reader.unpack_string(offset=string_offset, encoding="utf-16le") if string or not remove_empty_entries: self.entries[string_id] = string i += 1
def unpack(self, esd_reader: BinaryReader, **kwargs): header = esd_reader.unpack_struct(self.EXTERNAL_HEADER_STRUCT) # Internal offsets start here, so we reset the buffer. esd_reader = BinaryReader(esd_reader.read()) internal_header = esd_reader.unpack_struct(self.INTERNAL_HEADER_STRUCT) self.magic = internal_header["magic"] state_machine_headers = esd_reader.unpack_structs( self.STATE_MACHINE_HEADER_STRUCT, count=header["state_machine_count"]) for state_machine_header in state_machine_headers: states = self.State.unpack( esd_reader, state_machine_header["state_machine_offset"], count=state_machine_header["state_count"], ) self.state_machines[ state_machine_header["state_machine_index"]] = states if internal_header["esd_name_length"] > 0: esd_name_offset = internal_header["esd_name_offset"] esd_name_length = internal_header["esd_name_length"] # Note the given length is the length of the final string. The actual UTF-16 encoded bytes are twice that. self.esd_name = esd_reader.unpack_string(offset=esd_name_offset, length=2 * esd_name_length, encoding="utf-16le") esd_reader.seek(esd_name_offset + 2 * esd_name_length) self.file_tail = esd_reader.read() else: self.esd_name = "" esd_reader.seek(header["unk_offset_1"]) # after packed EZL self.file_tail = esd_reader.read()
def unpack_event_dict( cls, reader: BinaryReader, instruction_table_offset, base_arg_data_offset, event_arg_table_offset, event_layers_table_offset, count=1, ) -> dict[int, Event]: event_dict = {} struct_dicts = reader.unpack_structs(cls.HEADER_STRUCT, count=count) for d in struct_dicts: reader.seek(instruction_table_offset + d["first_instruction_offset"]) instruction_list = cls.Instruction.unpack( reader, base_arg_data_offset, event_layers_table_offset, count=d["instruction_count"]) reader.seek(event_arg_table_offset + d["first_event_arg_offset"]) event_args = cls.EventArg.unpack(reader, count=d["event_arg_count"]) for arg_r in event_args: # Attach event arg replacements to their instruction line. instruction_list[arg_r.line].event_args.append(arg_r) event_dict[d["event_id"]] = cls(d["event_id"], d["restart_type"], instruction_list) return event_dict
def unpack(cls, esd_reader: BinaryReader, state_machine_offset, count) -> dict[int, State]: """Unpack multiple states from the same state table. Returns a dictionary of states, because it's always possible (if yet unseen) that state indices are not contiguous. State 0 is not repeated, as it generally is in the packed table. """ state_dict = {} esd_reader.seek(state_machine_offset) struct_dicts = esd_reader.unpack_structs(cls.STRUCT, count=count) for d in struct_dicts: conditions = cls.Condition.unpack( esd_reader, d["condition_pointers_offset"], count=d["condition_pointers_count"], ) enter_commands = cls.Command.unpack( esd_reader, d["enter_commands_offset"], count=d["enter_commands_count"], ) exit_commands = cls.Command.unpack( esd_reader, d["exit_commands_offset"], count=d["exit_commands_count"], ) ongoing_commands = cls.Command.unpack( esd_reader, d["ongoing_commands_offset"], count=d["ongoing_commands_count"], ) # State 0 will be overwritten when repeated at the end of the table, rather than added. state_dict[d["index"]] = cls( d["index"], conditions, enter_commands, exit_commands, ongoing_commands, ) return state_dict
def unpack(cls, reader: BinaryReader, base_arg_data_offset, event_layers_table_offset, count=1): """Unpack some number of Instructions into a list, starting from the current file offset.""" instructions = [] struct_dicts = reader.unpack_structs(cls.HEADER_STRUCT, count=count) for i, d in enumerate(struct_dicts): # Process arguments. try: args_format, args_list = get_instruction_args( reader, d["category"], d["index"], base_arg_data_offset + d["first_base_arg_offset"], d["base_args_size"], cls.INSTRUCTION_ARG_TYPES, ) except KeyError: args_size = struct_dicts[ i + 1]["first_base_arg_offset"] - d["first_base_arg_offset"] reader.seek(base_arg_data_offset + d["first_base_arg_offset"]) raw_data = reader.read(args_size) _LOGGER.error( f"Error while processing instruction arguments. Raw arg data: {raw_data}" ) raise # Process event layers. if d["first_event_layers_offset"] > 0: event_layers = cls.EventLayers.unpack( reader, event_layers_table_offset + d["first_event_layers_offset"]) else: event_layers = None instructions.append( cls(d["category"], d["index"], args_format, args_list, event_layers)) return instructions
def unpack(cls, esd_reader: BinaryReader, commands_offset, count=1): """ Returns a list of Command instances. """ commands = [] if commands_offset == -1: return commands struct_dicts = esd_reader.unpack_structs(cls.STRUCT, count=count, offset=commands_offset) for d in struct_dicts: if d["args_offset"] > 0: esd_reader.seek(d["args_offset"]) arg_structs = cls.ARG_STRUCT.unpack_count(esd_reader, count=d["args_count"]) args = [ esd_reader.unpack_bytes(offset=a["arg_ezl_offset"], length=a["arg_ezl_size"]) for a in arg_structs ] else: args = [] commands.append(cls(d["bank"], d["index"], args)) return commands
def unpack_event_dict( cls, reader: BinaryReader, instruction_table_offset, base_arg_data_offset, event_arg_table_offset, event_layers_table_offset, count=1, ) -> dict[int, Event]: event_dict = {} struct_dicts = reader.unpack_structs(cls.HEADER_STRUCT, count=count) for d in struct_dicts: reader.seek(instruction_table_offset + d["first_instruction_offset"]) instruction_list = cls.Instruction.unpack( reader, base_arg_data_offset, event_layers_table_offset, count=d["instruction_count"]) reader.seek(event_arg_table_offset + d["first_event_arg_offset"]) event_args = cls.EventArg.unpack(reader, count=d["event_arg_count"]) for arg_r in event_args: # Attach event arg replacements to their instruction line. instruction_list[arg_r.line].event_args.append(arg_r) if event_id := d["event_id"] in event_dict: _LOGGER.warning( f"Event ID {event_id} appears multiple times in EMEVD file. Only the first one will be kept." ) else: event_dict[d["event_id"]] = cls(d["event_id"], d["restart_type"], instruction_list)
def unpack(cls, esd_reader: BinaryReader, condition_pointers_offset, count=1): """Returns a list of `Condition` instances`.""" conditions = [] if condition_pointers_offset == -1: return conditions pointers = esd_reader.unpack_structs(cls.POINTER_STRUCT, count=count, offset=condition_pointers_offset) for p in pointers: d = esd_reader.unpack_struct(cls.STRUCT, offset=p["condition_offset"]) pass_commands = cls.Command.unpack( esd_reader, d["pass_commands_offset"], count=d["pass_commands_count"], ) subconditions = cls.unpack( # safe recursion esd_reader, d["subcondition_pointers_offset"], count=d["subcondition_pointers_count"], ) test_ezl = esd_reader.unpack_bytes(offset=d["test_ezl_offset"], length=d["test_ezl_size"]) if d["next_state_offset"] > 0: next_state_index = esd_reader.unpack_struct( cls.STATE_ID_STRUCT, offset=d["next_state_offset"])["state_id"] else: next_state_index = -1 conditions.append( cls(next_state_index, test_ezl, pass_commands, subconditions)) return conditions
def unpack_fields( cls, param_name: str, paramdef_reader: BinaryReader, field_count: int, format_version: int, unicode: bool, byte_order: str, ) -> dict[str, ParamDefField]: """Buffer should be at the start of the packed fields (which are followed by the packed descriptions).""" field_structs = paramdef_reader.unpack_structs(cls.GET_FIELD_STRUCT( format_version, unicode, byte_order), count=field_count) fields = {} for field_index, field_struct in enumerate(field_structs): if field_struct["description_offset"] != 0: field_description = paramdef_reader.unpack_string( offset=field_struct["description_offset"], encoding="utf-16-le" if unicode else "shift_jis_2004", ) else: field_description = "" if "display_name_offset" in field_struct: display_name = paramdef_reader.unpack_string( offset=field_struct["display_name_offset"], encoding="utf-16-le", ) else: display_name = field_struct["display_name"] field = cls(field_struct, field_index, field_description, param_name, display_name=display_name) fields[field.name] = field return fields
def unpack(cls, reader: BinaryReader, count=1): event_args = [] struct_dicts = reader.unpack_structs(cls.HEADER_STRUCT, count=count) for d in struct_dicts: event_args.append(cls(**d)) return event_args
def unpack(self, reader: BinaryReader, **kwargs): self.byte_order = reader.byte_order = ">" if reader.unpack_value( "B", offset=44) == 255 else "<" version_info = reader.unpack("bbb", offset=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 = reader.unpack_struct(header_struct) try: self.param_type = header["param_type"] except KeyError: self.param_type = reader.unpack_string( 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. row_struct = self.ROW_STRUCT_64 if self.flags1.LongDataOffset else self.ROW_STRUCT_32 row_pointers = reader.unpack_structs(row_struct, count=header["row_count"]) row_data_offset = reader.position # 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 reader offset. name_encoding = self.get_name_encoding() for row_struct in row_pointers: reader.seek(row_struct["data_offset"]) row_data = reader.read(row_size) if row_struct["name_offset"] != 0: try: name = reader.unpack_string( 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 = reader.unpack_bytes( offset=row_struct["name_offset"], reset_old_offset=False, # no need to reset ) else: raise except ValueError: reader.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 reader.read(30))}" ) raise else: name = "" self.rows[row_struct["id"]] = ParamRow(row_data, self.paramdef, name=name)