Ejemplo n.º 1
0
    def _read(self, buffer, *args, **kwargs):
        # Should detect little endian byte order accordingly and remove the BOM
        data = buffer.read().decode('utf-16')
        match = self._regex_parse.match(data)

        if not match:
            raise ParserError(
                'Failed to find the base information. File may not be a .idt file or malformed.'
            )

        textures = TextureList()
        for tex_match in self._regex_texture.finditer(match.group('textures')):
            coordinates = CoordinateList()
            for coord_match in self._regex_coordinates.finditer(
                    tex_match.group('coordinates')):
                coordinates.append(CoordinateRecord(**coord_match.groupdict()))

            if len(coordinates) != int(tex_match.group('count')):
                raise ParserError(
                    'Amount of found coordinates (%s) does not match the amount of specified coordinates (%s)'
                    % (len(coordinates), tex_match.group('count')))

            textures.append(TextureRecord(tex_match.group('name'),
                                          coordinates))

        if len(textures) != int(match.group('texture_count')):
            raise ParserError(
                'Amount of found textures (%s) does not match the amount of specified textures (%s)'
                % (len(textures), match.group('texture_count')))

        self._records = textures
        self.version = int(match.group('version'))
        self.image = match.group('image')
Ejemplo n.º 2
0
 def __init__(self, parent, name=None, *args, **kwargs):
     super().__init__(*args, **kwargs)
     self.parent: 'AbstractKeyValueFile' = parent
     if name:
         self.name: str = name
     elif self.NAME:
         self.name: str = self.NAME
     else:
         raise ParserError('Missing name for section')
Ejemplo n.º 3
0
 def __init__(self, parent, name=None, *args, **kwargs):
     super().__init__(*args, **kwargs)
     self.parent = parent
     if name:
         self.name = name
     elif self.NAME:
         self.name = self.NAME
     else:
         raise ParserError('Missing name for section')
Ejemplo n.º 4
0
    def extract_dds(self, data: bytes) -> bytes:
        """
        Attempts to extract a .dds from the given data bytes.

        .dds files in the content.ggpk may be compressed with brotli or may be
        a reference to another .dds file.

        This function will take of those kind of files accordingly and try to return
        a file instead.
        If any problems arise an error will be raised instead.

        Parameters
        ----------
        data
            The raw data to extract the dds from.

        Returns
        -------
        bytes
            the uncompressed, dereferenced .dds file data

        Raises
        -------
        ValueError
            If the file data contains a reference, but path_or_ggpk is not specified
        TypeError
            If the file data contains a reference, but path_or_ggpk is of invalid
            type (i.e. not str or :class:`GGPKFile`
        ParserError
            If the uncompressed size does not match the size in the header
        brotli.error
            If whatever bytes were read were not brotli compressed
        """
        # Already a DDS file, so return it
        if data[:4] == b'DDS ':
            return data
        # Is this a reference?
        elif data[:1] == b'*':
            path = data[1:].decode()
            data = self.get_file(path)
            return self.extract_dds(data)
        else:
            size = int.from_bytes(data[:4], 'little')
            dec = brotli.decompress(data[4:])
            if len(dec) != size:
                raise ParserError(
                    'Decompressed size does not match size in the header')
            return dec
Ejemplo n.º 5
0
    def build_directory(self, parent: DirectoryNode = None) -> DirectoryNode:
        """
        Rebuilds the directory or the specified :class:`DirectoryNode`
        If the root directory is rebuild it will be stored in the directory
        object variable.
        
        Parameters
        ----------
        parent : :class:`DirectoryNode` or None
            parent :class:`DirectoryNode`. If None generate the root directory


        Returns
        -------
        DirectoryNode
            Returns the parent node or the root node if parent was None


        Raises
        ------
        ParserError
            if performed without calling .read() first
            if offsets pointing to records types which are not
            :class:`FileRecord` or :class:`DirectoryRecord`
        """
        if not self.records:
            raise ParserError('No records - perform .read() first')

        # Build Root directory
        if parent is None:
            ggpkrecord = self.records[0]
            for offset in ggpkrecord.offsets:
                record = self.records[offset]
                if isinstance(record, DirectoryRecord):
                    break
            if not isinstance(record, DirectoryRecord):
                raise ParserError('GGPKRecord does not contain a DirectoryRecord,\
                    got %s' % type(record))

            root = DirectoryNode(
                parent=None,
                is_file=False,
                record=record,
                hash=None,
            )

            self.directory = root
        else:
            root = parent

        l = []
        for entry in root.record.entries:
            l.append((entry.offset, entry.hash, root))

        try:
            while True:
                offset, hash, parent = l.pop()
                try:
                    record = self.records[offset]
                except KeyError:
                    pass
                else:
                    node = DirectoryNode(
                        parent=parent,
                        is_file=isinstance(record, FileRecord),
                        record=record,
                        hash=hash,
                    )
                    parent.children[record.name] = node

                    if node.is_directory:
                        for entry in record.entries:
                            l.append((entry.offset, entry.hash, node))
        except IndexError:
            pass

        return root
Ejemplo n.º 6
0
    def _read(self, buffer, *args, **kwargs):
        data = buffer.read().decode('utf-16')

        match = self._re_header.match(data)
        if match is None:
            raise ParserError(
                'File is not a valid %s file.' % self.__class__.__name__
            )

        self.version = int(match.group('version'))

        for section_match in self._re_find_kv_sections.finditer(
                match.group('remainder')):
            key = section_match.group('key')

            try:
                section = self[key]
            except KeyError:
                #print('Extra section:', key)
                section = AbstractKeyValueSection(parent=self, name=key)
                self[key] = section

            for kv_match in self._re_find_kv_pairs.finditer(
                    section_match.group('contents')):
                value = kv_match.group('value').strip('"')
                if value == 'true':
                    value = True
                elif value == 'false':
                    value = False
                else:
                    try:
                        value = int(value)
                    except ValueError:
                        try:
                            value = float(value)
                        except ValueError:
                            pass

                section[kv_match.group('key')] = value

        extend = match.group('extends')

        if extend == 'nothing':
            self.extends = None
        elif extend:
            if self._parent_file:
                self.merge(self._parent_file)
                if self._parent_file.name != extend:
                    warnings.warn(
                        'Parent file name "%s" doesn\'t match extended file '
                        'name "%s"' % (self._parent_file.name, extend),
                        ParserWarning,
                    )
            elif self._parent_file_system:
                obj = self.__class__(
                    parent_or_file_system=self._parent_file_system
                )
                obj.read(
                    file_path_or_raw=self._parent_file_system.get_file(
                        extend + self.EXTENSION
                    ),
                )
                self.merge(obj)
            else:
                raise ParserError(
                    'File extends "%s", but parent_or_file_system has not '
                    'been specified on class creation.' % extend
                )
            self.extends = extend
Ejemplo n.º 7
0
def extract_dds(data, path_or_ggpk=None):
    """
    Attempts to extract a .dds from the given data bytes.

    .dds files in the content.ggpk may be compressed with brotli or may be
    a reference to another .dds file.

    This function will take of those kind of files accordingly and try to return
    a file instead.
    If any problems arise an error will be raised instead.

    Parameters
    ----------
    data : bytes
        The raw data to extract the dds from.
    path_or_ggpk : str or GGPKFile
        A str containing the path where the extracted content.ggpk is located or
        an :class:`GGPKFile` instance

    Returns
    -------
    bytes
        the uncompressed, dereferenced .dds file data

    Raises
    -------
    ValueError
        If the file data contains a reference, but path_or_ggpk is not specified
    TypeError
        If the file data contains a reference, but path_or_ggpk is of invalid
        type (i.e. not str or :class:`GGPKFile`
    ParserError
        If the uncompressed size does not match the size in the header
    brotli.error
        If whatever bytes were read were not brotli compressed
    """
    # Already a DDS file, so return it
    if data[:4] == b'DDS ':
        return data
    # Is this a reference?
    elif data[:1] == b'*':
        path = data[1:].decode()
        if path_or_ggpk is None:
            raise ValueError(
                '.dds file is a reference, but path_or_ggpk is not specified.')
        elif isinstance(path_or_ggpk, GGPKFile):
            data = path_or_ggpk.directory[path].record.extract().read()
        elif isinstance(path_or_ggpk, str):
            with open(os.path.join(path_or_ggpk, path), 'rb') as f:
                data = f.read()
        else:
            raise TypeError('path_or_ggpk has an invalid type "%s" %' %
                            type(path_or_ggpk))
        return extract_dds(
            data,
            path_or_ggpk=path_or_ggpk,
        )
    else:
        size = int.from_bytes(data[:4], 'little')
        dec = brotli.decompress(data[4:])
        if len(dec) != size:
            raise ParserError(
                'Decompressed size does not match size in the header')
        return dec