Example #1
0
    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
Example #2
0
    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()
Example #3
0
    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
Example #4
0
    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
Example #5
0
    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
Example #6
0
    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
Example #7
0
    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)
Example #8
0
    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
Example #9
0
 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
Example #10
0
 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
Example #11
0
    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)