"""Stream UnityFS nodes to a private workspace; do not load media bundles into RAM.""" from pathlib import Path def unpack(source: Path, target: Path) -> list[Path]: from UnityPy.helpers.CompressionHelper import DECOMPRESSION_MAP from UnityPy.streams import EndianBinaryReader target.mkdir(parents=True, exist_ok=True) with source.open("rb") as stream: reader = EndianBinaryReader(stream) if reader.read_string_to_null() != "UnityFS": raise ValueError(f"Неподдерживаемый Unity-контейнер: {source.name}") version = reader.read_u_int() reader.read_string_to_null() reader.read_string_to_null() size = reader.read_long() packed, raw, flags = reader.read_u_int(), reader.read_u_int(), reader.read_u_int() if version not in {6, 7, 8} or flags & ~0x3ff: raise ValueError(f"Неподдерживаемый UnityFS: версия {version}, flags={flags}") if version >= 7: reader.align_stream(16) start = reader.Position if flags & 0x80: reader.Position = size - packed info = EndianBinaryReader(DECOMPRESSION_MAP[flags & 63](reader.read_bytes(packed), raw)) if flags & 0x80: reader.Position = start info.read_bytes(16) count = info.read_int() if not 0 <= count <= 1_000_000: raise ValueError("Некорректное количество блоков UnityFS") blocks = [(info.read_u_int(), info.read_u_int(), info.read_u_short()) for _ in range(count)] count = info.read_int() if not 0 <= count <= 100_000: raise ValueError("Некорректное количество файлов UnityFS") nodes = [(info.read_long(), info.read_long(), info.read_u_int(), info.read_string_to_null()) for _ in range(count)] if flags & 0x200: reader.align_stream(16) handles, paths = [], [] total = sum(b[0] for b in blocks) try: for offset, length, _, name in nodes: path = (target / name).resolve() if (not path.is_relative_to(target.resolve()) or path in paths or offset < 0 or length < 0 or offset + length > total): raise ValueError(f"Некорректный файл UnityFS: {name}") path.parent.mkdir(parents=True, exist_ok=True) handles.append((offset, length, path.open("wb"))) paths.append(path) position = 0 for raw_size, packed_size, block_flags in blocks: chunk = DECOMPRESSION_MAP[block_flags & 63](reader.read_bytes(packed_size), raw_size) if len(chunk) != raw_size: raise ValueError("Повреждённый блок UnityFS") for offset, length, handle in handles: left, right = max(position, offset), min(position + raw_size, offset + length) if left < right: handle.write(chunk[left - position:right - position]) position += raw_size finally: for _, _, handle in handles: handle.close() return paths