from base64 import b64decode from io import BytesIO from zipfile import ZipFile, ZIP_DEFLATED, BadZipFile from struct import pack from datetime import datetime from fnmatch import fnmatch from steam.enums import EDepotFileFlag from steam.core.crypto import symmetric_decrypt from steam.util.binary import StructReader from steam.protobufs.content_manifest_pb2 import (ContentManifestMetadata, ContentManifestPayload, ContentManifestSignature) class DepotManifest(object): PROTOBUF_PAYLOAD_MAGIC = 0x71F617D0 PROTOBUF_METADATA_MAGIC = 0x1F4812BE PROTOBUF_SIGNATURE_MAGIC = 0x1B81B817 PROTOBUF_ENDOFMANIFEST_MAGIC = 0x32C415AB def __init__(self, data=None): """Manage depot manifest :param data: manifest data :type data: bytes """ self.metadata = ContentManifestMetadata() self.payload = ContentManifestPayload() self.signature = ContentManifestSignature() if data: self.deserialize(data) def __repr__(self): params = ', '.join([ str(self.depot_id), str(self.gid), repr(datetime.utcfromtimestamp(self.metadata.creation_time).isoformat().replace('T', ' ')), ]) if self.metadata.filenames_encrypted: params += ', filenames_encrypted=True' return "<%s(%s)>" % ( self.__class__.__name__, params, ) @property def depot_id(self): return self.metadata.depot_id @property def gid(self): return self.metadata.gid_manifest @property def creation_time(self): return self.metadata.creation_time @property def size_original(self): return self.metadata.cb_disk_original @property def size_compressed(self): return self.metadata.cb_disk_compressed def decrypt_filenames(self, depot_key): """Decrypt all filenames in the manifest :param depot_key: depot key :type depot_key: bytes :raises: :class:`RuntimeError` """ if not self.metadata.filenames_encrypted: return for mapping in self.payload.mappings: filename = b64decode(mapping.filename) try: filename = symmetric_decrypt(filename, depot_key) except Exception: RuntimeError("Unable to decrypt filename for depot manifest") mapping.filename = filename self.metadata.filenames_encrypted = False def deserialize(self, data): """Deserialize a manifest (compressed or uncompressed) :param data: manifest data :type data: bytes """ try: with ZipFile(BytesIO(data)) as zf: data = zf.read(zf.filelist[0]) except BadZipFile: pass data = StructReader(data) magic, length = data.unpack('" % ( self.__class__.__name__, self.manifest.depot_id, self.manifest.gid, repr(self.filename), 'is_directory=True' if self.is_directory else self.size, ) @property def filename(self): return self.file_mapping.filename.rstrip('\x00 \n\t') @property def size(self): return self.file_mapping.size @property def chunks(self): return self.file_mapping.chunks @property def flags(self): return self.file_mapping.flags @property def is_directory(self): return self.flags & EDepotFileFlag.Directory > 0 @property def is_file(self): return not self.is_directory