#!/usr/bin/env python3 """Зонд чтения флеша Beken-загрузчика модуля: catch link-check -> один read4k(0) -> сырой ответ. Фрейминг (из DMR Upgrade Tool SendEraseFlashReq): 01 E0 FC FF F4 ... Erase=0x0F. Read4K пробуем CMD 0x09: 01 E0 FC FF F4 05 00 09 . ТОЛЬКО ЧТЕНИЕ. Процедура: запусти, затем зажми MENU и включи рацию. Тул поймает загрузчик и прочитает 1 сектор по адресу 0 — покажет формат ответа (по нему построим полный дамп). """ import argparse, subprocess PS = r''' $p=New-Object System.IO.Ports.SerialPort "{port}",115200,"None",8,"one" $p.ReadTimeout=1; $p.WriteTimeout=50; $p.ReadBufferSize=16384 try{{$p.Open()}}catch{{Write-Output "ERR:$($_.Exception.Message)";exit}} $lc=[byte[]]@(1,224,252,1,0) # read4k addr=0: 01 E0 FC FF F4 05 00 09 00 00 00 00 $rd=[byte[]]@(1,224,252,255,244,5,0,{cmd},0,0,0,0) $sw=[Diagnostics.Stopwatch]::StartNew(); $state="catch"; $roll="" while($sw.Elapsed.TotalSeconds -lt {sec}){{ if($state -eq "catch"){{ try{{$p.Write($lc,0,$lc.Length)}}catch{{}} $n=$p.BytesToRead if($n -gt 0){{ $b=New-Object byte[] $n;$p.Read($b,0,$n)|Out-Null $hex=($b|ForEach-Object{{"{{0:x2}}"-f $_}}) -join "" $roll=($roll+$hex); if($roll.Length -gt 400){{$roll=$roll.Substring($roll.Length-400)}} if($hex -match "484b4c46"){{ Write-Output "BANNER" }} if($roll -match "040e05"){{ Write-Output "CATCH" # слить бэклог link-check ответов до тишины (~250мс без байт) $q=[Diagnostics.Stopwatch]::StartNew(); $last=0 while($q.ElapsedMilliseconds -lt 400){{ $m=$p.BytesToRead if($m -gt 0){{ $tb=New-Object byte[] $m;$p.Read($tb,0,$m)|Out-Null; $last=$q.ElapsedMilliseconds }} }} $p.DiscardInBuffer() $p.Write($rd,0,$rd.Length) # read4k $state="read"; $rsw=[Diagnostics.Stopwatch]::StartNew() $acc=New-Object System.Collections.Generic.List[byte] }} }} }} else {{ $n=$p.BytesToRead if($n -gt 0){{ $b=New-Object byte[] $n;$p.Read($b,0,$n)|Out-Null; foreach($x in $b){{$acc.Add($x)}} }} if($rsw.Elapsed.TotalMilliseconds -gt 2500){{ $hx=($acc.ToArray()|ForEach-Object{{"{{0:x2}}"-f $_}}) -join "" Write-Output ("RDLEN:"+$acc.Count) Write-Output ("RDHEAD:"+$hx.Substring(0,[Math]::Min(96,$hx.Length))) if($hx.Length -gt 96){{ Write-Output ("RDTAIL:"+$hx.Substring($hx.Length-32)) }} break }} }} }} $p.Close(); Write-Output "END" ''' def main(): ap=argparse.ArgumentParser() ap.add_argument("--port",default="COM4"); ap.add_argument("--sec",type=int,default=30) ap.add_argument("--cmd",default="9",help="опкод чтения (9=read4k, 3=readsector)") a=ap.parse_args() print(f"[зонд чтения, CMD 0x{int(a.cmd):02x}, {a.port}, {a.sec}с] — ЗАЖМИ MENU и включи рацию\n") r=subprocess.run(["powershell.exe","-NoProfile","-Command", PS.format(port=a.port,sec=a.sec,cmd=int(a.cmd))],capture_output=True,text=True) for line in r.stdout.replace("\r","").split("\n"): line=line.strip() if line.startswith(("ERR:","CATCH","RDLEN:","RDHEAD:","RDTAIL:")): print(" "+line) print("\n(RDLEN ~4096+ и RDHEAD с данными = read4k работает. RDLEN 0/мало = опкод неверный, пробуй --cmd 3)") if __name__=="__main__": main()