#!/usr/bin/env python3 """Decode a captured Blaze Fire2 frame: 16-byte header + Heat2 TDF payload. Clean-room: parses the wire bytes of our own client's traffic.""" import sys, struct def decode_tag(b): # Heat2 tag: 3 bytes -> 4 chars, each 6-bit; 0 -> ' ' (trimmed). char = v ? v+0x20 : ' ' a,b1,c = b[0],b[1],b[2] v=[ (a>>2)&0x3f, ((a&0x3)<<4)|((b1>>4)&0xf), ((b1&0xf)<<2)|((c>>6)&0x3), c&0x3f ] return ''.join(chr(x+0x20) if x else ' ' for x in v).rstrip() TYPES={0x00:'int',0x01:'string',0x02:'blob',0x03:'struct',0x04:'list', 0x05:'map',0x06:'union',0x07:'intlist',0x08:'objtype',0x09:'objid',0x0a:'float'} def read_varint(buf,i): # Heat2 varint: 7 bits/byte, high bit = continue; first byte only 6 data bits (bit6=continue) b=buf[i]; i+=1 val=b&0x3f if b&0x80: shift=6 while True: b=buf[i]; i+=1 val|=(b&0x7f)<end: print(f"{pad}[trailing {buf[i:end].hex()}]"); break tag=decode_tag(buf[i:i+3]); typ=buf[i+3]; i+=4 tn=TYPES.get(typ,f'0x{typ:02x}') if typ==0x00: # int varint v,i=read_varint(buf,i); print(f"{pad}{tag} (int) = {v}") elif typ==0x01: # string: varint len + bytes (incl null) ln,i=read_varint(buf,i); s=buf[i:i+ln]; i+=ln print(f"{pad}{tag} (str) = {s.rstrip(bytes([0])).decode(errors='replace')!r}") elif typ==0x02: # blob ln,i=read_varint(buf,i); print(f"{pad}{tag} (blob[{ln}]) = {buf[i:i+ln].hex()}"); i+=ln elif typ==0x03: # struct: nested until 0x00 terminator print(f"{pad}{tag} (struct) {{") i=walk(buf,depth+1,i,end) # walk handles 0x00 term print(f"{pad}}}") else: # unknown/complex: dump remainder briefly and stop this level print(f"{pad}{tag} ({tn}) {buf[i:min(i+24,end)].hex()}") # best-effort: skip nothing, bail to avoid misparse return end if iI',data[0:4])[0] comp=struct.unpack('>H',data[6:8])[0] cmd=struct.unpack('>H',data[8:10])[0] err=struct.unpack('>H',data[10:12])[0] mtyp=data[12] print(f"== {sys.argv[1]} ==") print(f"Fire2 header: len={ln} component=0x{comp:04x} command=0x{cmd:04x} error=0x{err:04x} msgtype=0x{mtyp:02x}") print(f"payload ({len(data)-16} bytes):") walk(data[16:]) main()