Files
OpenFUT/fifa17-recon/docker/fifa17-python/tools/decode_fire2.py
T
root 70a64e3709 fifa17-python: commit working FUT backend deployment (client/server split)
Freeze the running offline FUT backend into version control as
fifa17-recon/docker/fifa17-python/ - declarative and rebuildable from a
fresh checkout:

* OPENFUT_BIND / OPENFUT_ADVERTISE client/server split in the responders
  (lsx, blaze, roster, utas, pow) + entrypoint.sh; OPENFUT_ADVERTISE is
  required for remote mode (compose and entrypoint fail without it)
* docker-compose.yml reproducing the frozen baseline container exactly
  (env, ports incl. the 8085->8080 POW-content remap, /state bind, restart)
* .env.example / .env for site config - the LAN IP is never hardcoded in source
* tools/ + data/ staged from openfut-fut-backend:python-baseline-2026-08-10,
  verified byte-identical to the running container at freeze time
* client_arm.sh (the 105 client-side arming counterpart)
* Dockerfile bakes /app/SHA256SUMS.txt so any image is self-identifying
* docs/BASELINE-python-2026-08-10.md: frozen image/container/hash record,
  restore instructions and rebuild-equivalence procedure

Secrets (redir key/cert, .env) and runtime state (docker/state) stay gitignored.
The live container is untouched pending the .105 launcher audit.
2026-08-10 23:54:04 +00:00

68 lines
2.7 KiB
Python

#!/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)<<shift; shift+=7
if not (b&0x80): break
return val,i
def walk(buf, depth=0, i=0, end=None):
if end is None: end=len(buf)
pad=' '*depth
while i < end:
if i+4>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}) <complex; raw from here> {buf[i:min(i+24,end)].hex()}")
# best-effort: skip nothing, bail to avoid misparse
return end
if i<end and buf[i]==0x00: # struct terminator
i+=1; return i
return i
def main():
data=open(sys.argv[1],'rb').read()
ln=struct.unpack('>I',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()