Add Resource.compressed_info attribute

This allows accessing a compressed resource's header data, without
having to decompress it or parse the compressed data manually.
This commit is contained in:
dgelessus 2019-09-23 23:50:29 +02:00
parent a23cd0fcb2
commit 868a322b8e
2 changed files with 33 additions and 11 deletions

View File

@ -96,7 +96,7 @@ class ResourceAttrs(enum.Flag):
class Resource(object):
"""A single resource from a resource file."""
__slots__ = ("type", "id", "name", "attributes", "data_raw", "_data_decompressed")
__slots__ = ("type", "id", "name", "attributes", "data_raw", "_compressed_info", "_data_decompressed")
def __init__(self, resource_type: bytes, resource_id: int, name: typing.Optional[bytes], attributes: ResourceAttrs, data_raw: bytes):
"""Create a new resource with the given type code, ID, name, attributes, and data."""
@ -138,6 +138,22 @@ class Resource(object):
warnings.warn(DeprecationWarning("The resource_id attribute has been deprecated and will be removed in a future version. Please use the id attribute instead."))
return self.id
@property
def compressed_info(self) -> typing.Optional[compress.common.CompressedHeaderInfo]:
"""The compressed resource header information, or None if this resource is not compressed.
Accessing this attribute may raise a DecompressError if the resource data is compressed and the header could not be parsed. To access the unparsed header data, use the data_raw attribute.
"""
if ResourceAttrs.resCompressed in self.attributes:
try:
return self._compressed_info
except AttributeError:
self._compressed_info = compress.common.CompressedHeaderInfo.parse(self.data_raw)
return self._compressed_info
else:
return None
@property
def data(self) -> bytes:
"""The resource data, decompressed if necessary.
@ -149,7 +165,7 @@ class Resource(object):
try:
return self._data_decompressed
except AttributeError:
self._data_decompressed = compress.decompress(self.data_raw)
self._data_decompressed = compress.decompress_parsed(self.compressed_info, self.data_raw[self.compressed_info.header_length:])
return self._data_decompressed
else:
return self.data_raw

View File

@ -19,6 +19,20 @@ DECOMPRESSORS = {
}
def decompress_parsed(header_info: CompressedHeaderInfo, data: bytes, *, debug: bool=False) -> bytes:
"""Decompress the given compressed resource data, whose header has already been removed and parsed into a CompressedHeaderInfo object."""
try:
decompress_func = DECOMPRESSORS[header_info.dcmp_id]
except KeyError:
raise DecompressError(f"Unsupported 'dcmp' ID: {header_info.dcmp_id}")
decompressed = decompress_func(header_info, data, debug=debug)
if len(decompressed) != header_info.decompressed_length:
raise DecompressError(f"Actual length of decompressed data ({len(decompressed)}) does not match length stored in resource ({header_info.decompressed_length})")
return decompressed
def decompress(data: bytes, *, debug: bool=False) -> bytes:
"""Decompress the given compressed resource data."""
@ -27,12 +41,4 @@ def decompress(data: bytes, *, debug: bool=False) -> bytes:
if debug:
print(f"Compressed resource data header: {header_info}")
try:
decompress_func = DECOMPRESSORS[header_info.dcmp_id]
except KeyError:
raise DecompressError(f"Unsupported 'dcmp' ID: {header_info.dcmp_id}")
decompressed = decompress_func(header_info, data[header_info.header_length:], debug=debug)
if len(decompressed) != header_info.decompressed_length:
raise DecompressError(f"Actual length of decompressed data ({len(decompressed)}) does not match length stored in resource ({header_info.decompressed_length})")
return decompressed
return decompress_parsed(header_info, data[header_info.header_length:], debug=debug)