#!/usr/bin/env python3 """ p1_unpack.py — Извлечение потоков из контейнера 1С v8 (.hbk) """ import json import os import struct import sys BLOCK_HDR = 31 # размер блочного адреса NO_NEXT = 0x7FFFFFFF # нет следующей страницы # ─── Чтение блочного адреса ────────────────────────────────────────── def parse_block_hdr(data, offset): """ Разбирает блочный адрес: \\r\\nDOCSIZE PAGESIZE NEXTPAGE \\r\\n Возвращает (doc_size, page_size, next_page) или None. """ if offset < 0 or offset + BLOCK_HDR > len(data): return None if ( data[offset : offset + 2] != b"\r\n" or data[offset + 29 : offset + 31] != b"\r\n" ): return None try: txt = data[offset + 2 : offset + 29].decode("ascii").strip().split() if len(txt) != 3: return None return int(txt[0], 16), int(txt[1], 16), int(txt[2], 16) except Exception: return None def read_block(data, offset): """ Читает ПОЛНОЕ содержимое блока (может быть цепочка страниц). Возвращает bytes. """ first = parse_block_hdr(data, offset) if first is None: return None doc_size = first[0] # реальный размер данных buf = bytearray() cur = offset visited = set() while cur != NO_NEXT and cur not in visited: visited.add(cur) hdr = parse_block_hdr(data, cur) if hdr is None: break _, page_size, next_page = hdr start = cur + BLOCK_HDR buf.extend(data[start : start + page_size]) cur = next_page return bytes(buf[:doc_size]) # ─── Разбор метаданных потока ──────────────────────────────────────── def parse_meta(raw): """ Метаданные потока: creation FILETIME (8 байт) modified FILETIME (8 байт) attrs uint32 (4 байта) name UTF-16LE с нулевым терминатором """ if not raw or len(raw) < 20: return None, raw name = raw[20:].decode("utf-16-le").rstrip("\x00") return name, raw # ─── Основная функция ──────────────────────────────────────────────── def extract(filepath, out_dir="hbk_streams"): with open(filepath, "rb") as f: data = f.read() magic, page_sz, version, _ = struct.unpack_from("