#!/usr/bin/env python3
"""Снять CRC16 из сырого дампа Beken-флеша (FM100B/HF6853 модуль RT-4D).

Формат Beken (bootloader/app партиции): каждые 32 байта данных сопровождаются 2 байтами
CRC16 -> физические блоки по 34 байта. Сырой дамп/образ (FM100B_*.bin) содержит эти CRC,
что «рвёт» строки и код. Этот тул выкидывает 2 CRC-байта из каждого 34-байтного блока ->
чистая прошивка для реверса.

Подтверждено на FM100B_V1.2.0.32: непечатные пары строго через 34 байта; после снятия
строки («RF Firmware», «check failed», «beken378\\intercom») становятся целыми.

    python3 bk_deinterleave.py FM100B_raw.bin FM100B_clean.bin
    python3 bk_deinterleave.py --crc FM100B_raw.bin   # только проверить период CRC

Совместимо по формату с ltchiptool/bk7231tools (они делают то же при чтении флеша).
"""
import sys, argparse

BLK = 34      # 32 данных + 2 CRC16
DATA = 32

def deinterleave(raw: bytes, phase: int = 0) -> bytes:
    out = bytearray(raw[:phase])
    body = raw[phase:]
    for i in range(0, len(body), BLK):
        out += body[i:i+DATA]
    return bytes(out)

def find_crc_period(raw: bytes, start=0, length=0x400):
    """вернуть наиболее частый шаг между непечатными байтами (ожидаем 34)"""
    from collections import Counter
    poss = [i for i in range(start, min(start+length, len(raw)))
            if not (32 <= raw[i] < 127 or raw[i] in (0, 10, 13))]
    steps = Counter(b-a for a, b in zip(poss, poss[1:]))
    return steps.most_common(5)

def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("infile")
    ap.add_argument("outfile", nargs="?")
    ap.add_argument("--phase", type=int, default=0, help="смещение начала блоков (обычно 0)")
    ap.add_argument("--crc", action="store_true", help="только показать период CRC")
    a = ap.parse_args()
    raw = open(a.infile, "rb").read()
    if a.crc or not a.outfile:
        # поискать текстовую область для оценки периода
        for s in (0x15c000, 0x100000, 0x80000, 0):
            if s < len(raw):
                p = find_crc_period(raw, s)
                if p and p[0][0] == BLK:
                    print(f"CRC-период {BLK} подтверждён @0x{s:x}: {p}")
                    break
        else:
            print("период 34 не найден — возможно, образ уже чистый или другой формат")
        if not a.outfile:
            return
    clean = deinterleave(raw, a.phase)
    open(a.outfile, "wb").write(clean)
    print(f"{a.infile} ({len(raw)}) -> {a.outfile} ({len(clean)}), снято {len(raw)-len(clean)} CRC-байт")

if __name__ == "__main__":
    main()
