Files
pgames-manager/backend/app/converter/unity_bundle.py
T
mrleo1nidandClaude Sonnet 5.5 bbbcc6a5d8 chore: add CI, lint config, login rate limit and split large modules
- Rate-limit failed logins (8 per 15 min per IP or username, 429 + Retry-After)
- Add ruff/mypy config and GitHub Actions workflow (ruff, pytest, tsc, tests, build)
- Apply ruff autofixes (import order, pyupgrade)
- Extract rpy_helpers.py, engine-util.ts, TranslationParts.tsx and
  translation-types.ts from oversized modules
- Add dependency-free React UI tests
- Refresh README and ARCHITECTURE (Naninovel/Tomie, status and plans)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 11:23:22 +03:00

66 lines
3.2 KiB
Python

"""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