Полный реверс протокола калибровки тюнера (IL-декомпиляция)

- research/tuning-re/cil_disasm.py — минимальный CIL-дизассемблер (dnfile).
- research/tuning-re/command-templates.txt — извлечённые шаблоны всех команд.
- docs/tuning-software-protocol.md: точная карта команд-полей + раскладка bufDigTune
  + ВЫВОД: частотная калибровка = ЗАМКНУТЫЙ ЦИКЛ (Set-команды несут band/опорные
  частоты, не абсолютное смещение; WriteAll данных не несёт). Слепой скрипт записи
  freq-трима может снова сбить калибровку -> безопаснее гнать штатным тулом с MMDVM
  BER вместо частотомера, либо сперва рид-онли снять текущую калибровку.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NikMGoqQWWk9wy2ww2vJAr
Этот коммит содержится в:
2026-07-24 03:39:59 +09:00
co-authored by Claude Opus 4.8
родитель dfae06acfc
Коммит 87df47cb94
3 изменённых файлов: 176 добавлений и 4 удалений
+98
Просмотреть файл
@@ -0,0 +1,98 @@
import dnfile, struct
pe = dnfile.dnPE("tuning.exe")
net = pe.net
data = bytes(pe.__data__)
def rva2off(rva):
for s in pe.sections:
vs=max(s.Misc_VirtualSize,s.SizeOfRawData)
if s.VirtualAddress<=rva<s.VirtualAddress+vs:
return s.PointerToRawData+(rva-s.VirtualAddress)
def sstr(x):
try: return str(x)
except: return "?"
# token resolvers
mdef=net.mdtables.MethodDef.rows
mref=net.mdtables.MemberRef.rows if net.mdtables.MemberRef else []
fld=net.mdtables.Field.rows if net.mdtables.Field else []
tref=net.mdtables.TypeRef.rows if net.mdtables.TypeRef else []
def us(tok):
# #US token 0x70xxxxxx
idx=tok&0xffffff
try:
return '"'+sstr(net.user_strings.get(idx).value)+'"'
except Exception as e:
return f"US#{idx:x}"
def resolve(tok):
t=tok>>24; i=(tok&0xffffff)
if t==0x0A and i-1<len(mref): return "ref:"+sstr(mref[i-1].Name)
if t==0x06 and i-1<len(mdef): return "mdef:"+sstr(mdef[i-1].Name)
if t==0x04 and i-1<len(fld): return "fld:"+sstr(fld[i-1].Name)
if t==0x0A: return f"memberref#{i}"
return f"tok0x{tok:08x}"
# opcode table (subset)
OPS={0x00:"nop",0x01:"break",0x02:"ldarg.0",0x03:"ldarg.1",0x04:"ldarg.2",0x05:"ldarg.3",
0x06:"ldloc.0",0x07:"ldloc.1",0x08:"ldloc.2",0x09:"ldloc.3",0x0a:"stloc.0",0x0b:"stloc.1",
0x0c:"stloc.2",0x0d:"stloc.3",0x0e:("ldarg.s",1),0x0f:("ldarga.s",1),0x10:("starg.s",1),
0x11:("ldloc.s",1),0x12:("ldloca.s",1),0x13:("stloc.s",1),0x14:"ldnull",0x15:"ldc.i4.m1",
0x16:"ldc.i4.0",0x17:"ldc.i4.1",0x18:"ldc.i4.2",0x19:"ldc.i4.3",0x1a:"ldc.i4.4",0x1b:"ldc.i4.5",
0x1c:"ldc.i4.6",0x1d:"ldc.i4.7",0x1e:"ldc.i4.8",0x1f:("ldc.i4.s",1),0x20:("ldc.i4",4),
0x21:("ldc.i8",8),0x22:("ldc.r4",4),0x23:("ldc.r8",8),0x25:"dup",0x26:"pop",
0x28:("call",4,"tok"),0x29:("calli",4),0x2a:"ret",
0x2b:("br.s",1,"br"),0x2c:("brfalse.s",1,"br"),0x2d:("brtrue.s",1,"br"),0x2e:("beq.s",1,"br"),
0x2f:("bge.s",1,"br"),0x30:("bgt.s",1,"br"),0x31:("ble.s",1,"br"),0x32:("blt.s",1,"br"),
0x33:("bne.un.s",1,"br"),0x38:("br",4,"br"),0x39:("brfalse",4,"br"),0x3a:("brtrue",4,"br"),
0x3b:("beq",4,"br"),0x58:"add",0x59:"sub",0x5a:"mul",0x5b:"div",0x5f:"and",0x60:"or",
0x61:"xor",0x62:"shl",0x63:"shr",0x65:"neg",0x66:"not",
0x6f:("callvirt",4,"tok"),0x72:("ldstr",4,"us"),0x73:("newobj",4,"tok"),
0x74:("castclass",4,"tok"),0x75:("isinst",4,"tok"),
0x7b:("ldfld",4,"tok"),0x7d:("stfld",4,"tok"),0x7e:("ldsfld",4,"tok"),0x80:("stsfld",4,"tok"),
0x8c:("box",4,"tok"),0x8d:("newarr",4,"tok"),0x8e:"ldlen",
0x91:"ldelem.i1",0x92:"ldelem.u1",0x93:"ldelem.i2",0x94:"ldelem.u2",0x95:"ldelem.i4",
0x9c:"stelem.i1",0x9d:"stelem.i2",0x9e:"stelem.i4",0xa2:"stelem.ref",0x8f:("ldelema",4,"tok"),
0xa3:("ldelem",4,"tok"),0xa4:("stelem",4,"tok"),
0x6a:"conv.i8",0x69:"conv.i4",0x67:"conv.i1",0x68:"conv.i2",0xd1:"conv.u2",0xd2:"conv.u1",
0x20:("ldc.i4",4)}
def disasm(rva, name):
off=rva2off(rva)
b0=data[off]
if (b0&3)==2:
codesize=b0>>2; ilstart=off+1
else:
# fat header 12 bytes
codesize=struct.unpack_from("<I",data,off+4)[0]; ilstart=off+12
il=data[ilstart:ilstart+codesize]
print(f"\n===== {name} (RVA 0x{rva:x}, {codesize} bytes IL) =====")
p=0
while p<len(il):
op=il[p]; p0=p; p+=1
if op==0xfe:
op2=il[p]; p+=1
nm={0x01:"ceq",0x02:"cgt",0x04:"clt",0x09:("ldarg",2),0x0b:("ldloc",2),0x0c:("ldloca",2),0x0d:("stloc",2),0x16:"constrained",0x1e:"readonly"}.get(op2,f"fe{op2:02x}")
if isinstance(nm,tuple):
sz=nm[1]; val=int.from_bytes(il[p:p+sz],'little'); p+=sz
print(f" {p0:04x}: {nm[0]} {val}")
else:
print(f" {p0:04x}: {nm}")
continue
info=OPS.get(op)
if info is None:
print(f" {p0:04x}: .byte 0x{op:02x}"); continue
if isinstance(info,str):
print(f" {p0:04x}: {info}"); continue
nm=info[0]; sz=info[1]; kind=info[2] if len(info)>2 else None
raw=il[p:p+sz]; p+=sz
if kind=="tok":
tok=int.from_bytes(raw,'little'); print(f" {p0:04x}: {nm} {resolve(tok)}")
elif kind=="us":
tok=int.from_bytes(raw,'little'); print(f" {p0:04x}: {nm} {us(tok)}")
elif kind=="br":
disp=int.from_bytes(raw,'little',signed=True); print(f" {p0:04x}: {nm} -> {p+disp:04x}")
else:
val=int.from_bytes(raw,'little',signed=(nm in('ldc.i4','ldc.i4.s'))); print(f" {p0:04x}: {nm} {val} (0x{val&0xffffffff:x})")
import sys
for nm,rva in [("btnRead_U_Freq_Click",0x5f330),("btnCal_U_Freq_Click",0x5f53c),
("num_U_DigFreq_ValueChanged",0x5f160)]:
disasm(rva,nm)