Documenta il formato PSB dei file Creo e aggiungi il parser di riferimento
Reverse engineering di 9253000117a.prt.35: contenitore (offset dall'ultimo campo dell'intestazione, indice a catena), grammatica completa delle sezioni (id progressivi dei campi, tabella "dopo X viene Y", marcatori di chiusura), tabella dei double verificata sulla norma delle matrici, sezioni compresse e catalogo del contenuto. Parser Python in scripts/psb/. docs/history.md raccoglie stato, metodo e punti aperti per riprendere il lavoro. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
1 parent
1eac3ccaa7
commit
2d7ace07e2
15 files changed
+1309
No files matched your search
@@ -0,0 +1,25 @@
|
||||
import sys,os,glob,collections
|
||||
sys.path.insert(0,os.path.dirname(os.path.abspath(__file__)))
|
||||
import prt,psb
|
||||
files=sorted(glob.glob(sys.argv[1]))
|
||||
tot=collections.Counter(); errs=collections.Counter(); bygen=collections.defaultdict(collections.Counter)
|
||||
samples={}
|
||||
for f in files:
|
||||
try: d,secs,order=prt.load(f)
|
||||
except Exception as e:
|
||||
print('LOADERR',f,e); continue
|
||||
gen=d[:60].split()[2].decode()
|
||||
for n in order:
|
||||
o,l,fl=secs[n]
|
||||
typ=fl[2]
|
||||
if typ!='9': bygen[gen]['type_'+typ]+=1; continue
|
||||
b=d[o:o+l]
|
||||
p,root,e=psb.parse(b)
|
||||
full = p.p==len(b)
|
||||
if e is None and full: bygen[gen]['ok']+=1; bygen[gen]['okbytes']+=l
|
||||
else:
|
||||
bygen[gen]['err']+=1; bygen[gen]['errbytes']+=l
|
||||
key=(n.split('#')[0], str(e).split('@')[0][:50])
|
||||
errs[key]+=1; samples.setdefault(key,(os.path.basename(f),str(e)))
|
||||
for g,c in sorted(bygen.items()): print(g,dict(c))
|
||||
for k,v in errs.most_common(40): print(v,k,samples[k])
|
||||
@@ -0,0 +1,9 @@
|
||||
import sys,os
|
||||
sys.path.insert(0,os.path.dirname(os.path.abspath(__file__)))
|
||||
import prt
|
||||
for a in sys.argv[1:]:
|
||||
f,sec,off=a.split(',')
|
||||
off=int(off,16)
|
||||
d,secs,order=prt.load(f); o,l,_=secs[sec]; b=d[o:o+l]
|
||||
print('==',a)
|
||||
prt.hexdump(b,max(0,off-96),192)
|
||||
@@ -0,0 +1,30 @@
|
||||
import sys,os,math,collections
|
||||
sys.path.insert(0,os.path.dirname(os.path.abspath(__file__)))
|
||||
import prt,psb
|
||||
d,secs,order=prt.load(sys.argv[1])
|
||||
bad=collections.Counter(); good=0; unk=collections.Counter()
|
||||
def walk(v,name=''):
|
||||
global good
|
||||
if isinstance(v,psb.Node):
|
||||
for n,x in v: walk(x,n)
|
||||
elif isinstance(v,psb.Arr):
|
||||
for x in v: walk(x,name)
|
||||
elif isinstance(v,list) and v and isinstance(v[0],list) and len(v) in (3,4) and len(v[0])==3 and name in ('local_sys','orient','matrix'):
|
||||
for r in v[:3]:
|
||||
if any(isinstance(c,psb.Unk) for c in r):
|
||||
u=[c for c in r if isinstance(c,psb.Unk)]
|
||||
if len(u)==1:
|
||||
rest=sum(c*c for c in r if not isinstance(c,psb.Unk))
|
||||
unk[(u[0].code, round(math.sqrt(max(0,1-rest)),6))]+=1
|
||||
continue
|
||||
n=math.sqrt(sum(c*c for c in r))
|
||||
if abs(n-1)<1e-6: good+=1
|
||||
else: bad[(name,tuple(round(c,4) for c in r))]+=1
|
||||
for nme in order:
|
||||
o,l,f=secs[nme]
|
||||
if f[2]!='9': continue
|
||||
p,root,e=psb.parse(d[o:o+l])
|
||||
if root: walk(root[1])
|
||||
print('good rows',good,'bad',sum(bad.values()))
|
||||
for k,v in bad.most_common(15): print(v,k)
|
||||
print('unknown consts solved by norm:',unk.most_common(20))
|
||||
@@ -0,0 +1,11 @@
|
||||
import sys,os
|
||||
sys.path.insert(0,os.path.dirname(os.path.abspath(__file__)))
|
||||
import prt,psb
|
||||
for f in sys.argv[1:]:
|
||||
b=open(f,'rb').read()
|
||||
p,root,e=psb.parse(b)
|
||||
print(os.path.basename(f),len(b),p.p,'fields',p.nid,'lenient',p.lenient,'OK' if e is None else 'ERR '+repr(e))
|
||||
if e is not None: prt.hexdump(b,max(0,p.p-64),128)
|
||||
if root:
|
||||
out=[root[0]+':']; psb.fmt(root[1],1,out)
|
||||
open(f[:-4]+'.txt','w',encoding='utf-8').write('\n'.join(out))
|
||||
@@ -0,0 +1,40 @@
|
||||
import sys,re
|
||||
sys.stdout.reconfigure(encoding='utf-8',errors='replace')
|
||||
def load(p):
|
||||
d=open(p,'rb').read()
|
||||
base=int(d[:d.find(b'\n')].split()[-2],16)
|
||||
secs={}
|
||||
order=[]
|
||||
i=d.find(b'#UGC_TOC')
|
||||
while True:
|
||||
e=d.find(b'\n',i); pos=e+1; nxt=None
|
||||
while True:
|
||||
e2=d.find(b'\n',pos); line=d[pos:e2]
|
||||
if line.startswith(b'#'): break
|
||||
f=line.rstrip(b'#').split()
|
||||
name=f[0].decode('latin1'); off=int(f[1],16); ln=int(f[2],16)
|
||||
if name=='NEXT_TOC_ENTRY': nxt=base+off
|
||||
else:
|
||||
mk=len(name)+2
|
||||
secs[name]=(base+off+mk,ln-mk,[x.decode() for x in f[3:]]); order.append(name)
|
||||
pos=e2+1
|
||||
if nxt is None: break
|
||||
i=nxt
|
||||
return d,secs,order
|
||||
def sec(p,name):
|
||||
d,secs,_=load(p); o,l,_=secs[name]; return d[o:o+l]
|
||||
def hexdump(b,start=0,n=None,w=32):
|
||||
b=b[start:start+n] if n else b[start:]
|
||||
for i in range(0,len(b),w):
|
||||
c=b[i:i+w]
|
||||
print(f"{start+i:6x} "+' '.join(f"{x:02x}" for x in c).ljust(w*3)+' '+''.join(chr(x) if 32<=x<127 else '.' for x in c))
|
||||
if __name__=='__main__':
|
||||
d,secs,order=load(sys.argv[1])
|
||||
if len(sys.argv)>2:
|
||||
o,l,_=secs[sys.argv[2]]; b=d[o:o+l]
|
||||
st=int(sys.argv[3],0) if len(sys.argv)>3 else 0
|
||||
n=int(sys.argv[4],0) if len(sys.argv)>4 else None
|
||||
hexdump(b,st,n)
|
||||
else:
|
||||
for nme in order:
|
||||
o,l,f=secs[nme]; print(f"{nme:26} {o:8} {l:8} {' '.join(f):34} {d[o:o+20]!r}")
|
||||
@@ -0,0 +1,529 @@
|
||||
"""Parser generico delle sezioni a marcatori dei file Creo (.prt/.asm). Reverse engineering.
|
||||
Modello: ogni campo dichiarato per nome (e0 <tipo> nome\\0) riceve un id progressivo.
|
||||
In modalita' posizionale il campo atteso e' next[ultimo id letto]; f7 <id> lo forza.
|
||||
"""
|
||||
import struct, sys, os
|
||||
|
||||
|
||||
class Err(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Node(list):
|
||||
"""struct: lista di (nome, valore)"""
|
||||
|
||||
def get(s, k, d=None):
|
||||
for n, v in s:
|
||||
if n == k:
|
||||
return v
|
||||
return d
|
||||
|
||||
def all(s, k):
|
||||
return [v for n, v in s if n == k]
|
||||
|
||||
|
||||
class Arr(list):
|
||||
pass
|
||||
|
||||
|
||||
class Unk(float):
|
||||
"""costante a un byte non ancora identificata"""
|
||||
def __new__(cls, code):
|
||||
o = float.__new__(cls, 'nan')
|
||||
o.code = code
|
||||
return o
|
||||
|
||||
def __repr__(s):
|
||||
return f"<c{s.code:02x}>"
|
||||
|
||||
|
||||
class P:
|
||||
def __init__(s, b, trace=False):
|
||||
s.b = b
|
||||
s.p = 0
|
||||
s.fields = {}
|
||||
s.nid = 0
|
||||
s.next = {}
|
||||
s.alias = {}
|
||||
s.stack = []
|
||||
s.trace = trace
|
||||
s.root = None
|
||||
s.last = None
|
||||
s.lenient = 0
|
||||
s.lastc = None
|
||||
s.popped = None
|
||||
|
||||
def u8(s):
|
||||
v = s.b[s.p]
|
||||
s.p += 1
|
||||
return v
|
||||
|
||||
def peek(s):
|
||||
return s.b[s.p] if s.p < len(s.b) else None
|
||||
|
||||
def rint(s):
|
||||
b = s.u8()
|
||||
if b < 0x80:
|
||||
return b
|
||||
if b < 0xc0:
|
||||
return (b & 0x3f) << 8 | s.u8()
|
||||
if b < 0xe0:
|
||||
v = (b & 0x1f) << 16 | s.b[s.p] << 8 | s.b[s.p + 1]
|
||||
s.p += 2
|
||||
return v
|
||||
if b == 0xe8:
|
||||
return s.u8()
|
||||
if b == 0xe9:
|
||||
v = int.from_bytes(s.b[s.p:s.p + 2], 'big')
|
||||
s.p += 2
|
||||
return v
|
||||
if b == 0xea:
|
||||
v = int.from_bytes(s.b[s.p:s.p + 3], 'big')
|
||||
s.p += 3
|
||||
return v
|
||||
if b == 0xeb:
|
||||
v = int.from_bytes(s.b[s.p:s.p + 4], 'big')
|
||||
s.p += 4
|
||||
return v
|
||||
if b == 0xf5:
|
||||
return s.rint() | 0x80000000
|
||||
if b == 0xf6:
|
||||
return -1
|
||||
raise Err(f"int tag {b:02x} @{s.p - 1:x}")
|
||||
|
||||
CONST = {0x17: -6.283185307179586, 0x14: 4.71238898038469, 0x0d: -0.5, 0x11: 3.141592653589793, 0x15: -3.141592653589793, 0x12: 1.5707963267948966, 0x16: -1.5707963267948966, 0x13: 6.283185307179586, 0x18: 0.0, 0x0f: 1.0, 0x0e: 0.5, 0x10: -1.0, 0x0c: 0.1, 0x0a: 0.01, 0x07: 0.001}
|
||||
|
||||
FBASE = 0x3f9d
|
||||
FCONST = {0x0b: 1.0, 0x28: -1.0}
|
||||
|
||||
def rflt(s):
|
||||
t = s.u8()
|
||||
if 0x30 <= t <= 0x4f:
|
||||
T = (0x37 + t - 0x30) if t < 0x40 else (0xb7 + t - 0x40)
|
||||
raw = bytes([T]) + s.b[s.p:s.p + 3]
|
||||
s.p += 3
|
||||
elif t == 0xea:
|
||||
raw = s.b[s.p:s.p + 3] + bytes(1)
|
||||
s.p += 3
|
||||
elif t == 0xeb:
|
||||
raw = s.b[s.p:s.p + 4]
|
||||
s.p += 4
|
||||
elif t == 0xe9:
|
||||
raw = s.b[s.p:s.p + 2] + bytes(2)
|
||||
s.p += 2
|
||||
elif t == 0xe8:
|
||||
raw = s.b[s.p:s.p + 1] + bytes(3)
|
||||
s.p += 1
|
||||
elif t == 0x00:
|
||||
return 0.0
|
||||
elif t in s.FCONST:
|
||||
return s.FCONST[t]
|
||||
elif t < 0x30:
|
||||
return Unk(t)
|
||||
elif 0x50 <= t <= 0xdf:
|
||||
raw = (s.FBASE + t).to_bytes(2, 'big') + s.b[s.p:s.p + 2]
|
||||
s.p += 2
|
||||
else:
|
||||
raise Err(f"flt tag {t:02x} @{s.p - 1:x}")
|
||||
return struct.unpack('>f', raw)[0]
|
||||
|
||||
def rbyte(s):
|
||||
b = s.u8()
|
||||
if b == 0xe8:
|
||||
return s.u8()
|
||||
return b
|
||||
|
||||
# prefissi negativi a 16 bit dei tag a5-df: bfd0..c00d meno tre buchi (bfd2, bfdf, bffe)
|
||||
NEG = [x for x in range(0xbfd0, 0xc00e) if x not in (0xbfd2, 0xbfdf, 0xbffe)]
|
||||
|
||||
def rdbl(s):
|
||||
t = s.u8()
|
||||
if t in s.CONST:
|
||||
return s.CONST[t]
|
||||
if t <= 0x16 and t > 0x03:
|
||||
return Unk(t)
|
||||
if t <= 0x03:
|
||||
c = s.u8()
|
||||
k = t + 1
|
||||
pl = s.b[s.p:s.p + k]
|
||||
s.p += k
|
||||
if c not in s.CONST:
|
||||
if 0x04 <= c <= 0x16:
|
||||
return Unk(c)
|
||||
raise Err(f"dbl const {c:02x} after modifier @{s.p - 1:x}")
|
||||
raw = bytearray(struct.pack('>d', s.CONST[c]))
|
||||
raw[8 - k:] = pl
|
||||
return struct.unpack('>d', bytes(raw))[0]
|
||||
if t == 0xed:
|
||||
v = struct.unpack('>d', s.b[s.p:s.p + 8])[0]
|
||||
s.p += 8
|
||||
return v
|
||||
if t == 0xea:
|
||||
v = struct.unpack('>d', s.b[s.p:s.p + 3] + bytes(5))[0]
|
||||
s.p += 3
|
||||
return v
|
||||
if t == 0xec:
|
||||
v = struct.unpack('>d', s.b[s.p:s.p + 7] + bytes(1))[0]
|
||||
s.p += 7
|
||||
return v
|
||||
if 0x19 <= t <= 0x48:
|
||||
if t <= 0x2f:
|
||||
T = 0x3c + (t - 0x17) // 5
|
||||
m = (t - 0x17) % 5
|
||||
else:
|
||||
T = 0xbc + (t - 0x30) // 5
|
||||
m = (t - 0x30) % 5
|
||||
if m == 4:
|
||||
pl = s.b[s.p:s.p + 2]
|
||||
s.p += 2
|
||||
raw = bytes([T]) + pl + b'\x00' * 5
|
||||
elif m == 3:
|
||||
pl = s.b[s.p:s.p + 2]
|
||||
s.p += 2
|
||||
raw = bytes([T]) + pl + pl[1:] * 5
|
||||
elif m == 2:
|
||||
pl = s.b[s.p:s.p + 7]
|
||||
s.p += 7
|
||||
raw = bytes([T]) + pl
|
||||
elif m == 1:
|
||||
pl = s.b[s.p:s.p + 6]
|
||||
s.p += 6
|
||||
raw = bytes([T]) + pl + b'\x00'
|
||||
else:
|
||||
pl = s.b[s.p:s.p + 3]
|
||||
s.p += 3
|
||||
raw = bytes([T]) + pl + bytes(4)
|
||||
return struct.unpack('>d', raw)[0]
|
||||
if 0x49 <= t <= 0xdf:
|
||||
hi = (0x3f75 + t) if t <= 0xa4 else s.NEG[t - 0xa5]
|
||||
pl = s.b[s.p:s.p + 6]
|
||||
s.p += 6
|
||||
return struct.unpack('>d', hi.to_bytes(2, 'big') + pl)[0]
|
||||
raise Err(f"dbl tag {t:02x} @{s.p - 1:x}")
|
||||
|
||||
def rstr(s):
|
||||
if s.peek() == 0xe1:
|
||||
s.p += 1
|
||||
return None
|
||||
e = s.b.index(0, s.p)
|
||||
v = s.b[s.p:e].decode('latin1')
|
||||
s.p = e + 1
|
||||
return v
|
||||
|
||||
def rarr(s, n, rd):
|
||||
out = []
|
||||
while len(out) < n:
|
||||
b = s.peek()
|
||||
if 0xe4 <= b <= 0xe6 and out:
|
||||
s.p += 1
|
||||
out += [out[-1]] * (b - 0xe3)
|
||||
elif b == 0xe7 and out:
|
||||
s.p += 1
|
||||
k = s.rint()
|
||||
out += [out[-1]] * k
|
||||
else:
|
||||
out.append(rd())
|
||||
if len(out) != n:
|
||||
raise Err(f"array overrun {len(out)}>{n} @{s.p:x}")
|
||||
return out
|
||||
|
||||
def scalar(s, f):
|
||||
t = f['t']
|
||||
if t in (0x0a, 0x03):
|
||||
if s.peek() == 0xf8:
|
||||
s.p += 1
|
||||
f['dim'] = (s.rint(),)
|
||||
return [s.rstr() for _ in range(f['dim'][0])]
|
||||
if f.get('dim'):
|
||||
out = []
|
||||
while len(out) < f['dim'][0] and 0x20 <= s.peek() < 0xe0:
|
||||
out.append(s.rstr())
|
||||
return out
|
||||
return s.rstr()
|
||||
rd = s.rdbl if t == 0x02 else s.rbyte if t in (0x07, 0x04) else s.rflt if t == 0x06 else s.rint
|
||||
b = s.peek()
|
||||
if b == 0xe1:
|
||||
s.p += 1
|
||||
return None
|
||||
if b == 0xf8:
|
||||
s.p += 1
|
||||
f['dim'] = (s.rint(),)
|
||||
elif b == 0xf9:
|
||||
s.p += 1
|
||||
f['dim'] = (s.rint(), s.rint())
|
||||
elif b == 0xfc:
|
||||
s.p += 1
|
||||
f['dim'] = (s.rint(),) + tuple(f['dim'][1:])
|
||||
elif b == 0xfa:
|
||||
s.p += 1
|
||||
f['dim'] = tuple(s.rint() for _ in range(s.rint()))
|
||||
d = f.get('dim')
|
||||
if d is None:
|
||||
return rd()
|
||||
n = 1
|
||||
for k in d:
|
||||
n *= k
|
||||
v = s.rarr(n, rd)
|
||||
if len(d) == 2:
|
||||
v = [v[i * d[1]:(i + 1) * d[1]] for i in range(d[0])]
|
||||
return v
|
||||
|
||||
def sch(s, fid):
|
||||
return s.alias.get(fid, fid)
|
||||
|
||||
# --- contenitori -------------------------------------------------
|
||||
def owner_index(s, E, post=False):
|
||||
own = s.fields[E]['own']
|
||||
for i in range(len(s.stack) - 1, -1, -1):
|
||||
c = s.stack[i]
|
||||
if c['kind'] == 'struct':
|
||||
if c['schema'] in own:
|
||||
if post and s.fields[E].get('post') and i > 0:
|
||||
return i - 1
|
||||
return i
|
||||
elif c['occ'] == E or ('A', c['occ']) in own:
|
||||
return i
|
||||
return None
|
||||
|
||||
def default_close(s):
|
||||
"""chiusura implicita prima di un campo con nome o di un salto senza marcatore f0-f4:
|
||||
se il campo atteso appartiene al contenitore padre di quello in cui e' stato scritto
|
||||
l'ultimo campo, quest'ultimo contenitore si considera chiuso."""
|
||||
if s.last is None or not s.stack or s.lastc is None:
|
||||
return
|
||||
E = s.next.get(s.last)
|
||||
if E is None:
|
||||
return
|
||||
p = None
|
||||
for k in range(len(s.stack) - 1, -1, -1):
|
||||
if s.stack[k] is s.lastc:
|
||||
p = k
|
||||
break
|
||||
if not p:
|
||||
return
|
||||
i = s.owner_index(E, True)
|
||||
if i == p - 1:
|
||||
del s.stack[p:]
|
||||
|
||||
def marker(s):
|
||||
b = s.peek()
|
||||
if b is None or not (0xf0 <= b <= 0xf4):
|
||||
return None
|
||||
s.p += 1
|
||||
if b == 0xf4:
|
||||
return s.rint()
|
||||
return b - 0xf0
|
||||
|
||||
def explicit(s, n):
|
||||
for _ in range(n):
|
||||
if not s.stack:
|
||||
raise Err(f"pop underflow @{s.p:x}")
|
||||
s.popped = s.stack.pop()
|
||||
|
||||
def trans(s, E):
|
||||
if s.last is not None:
|
||||
s.next[s.last] = E
|
||||
s.last = E
|
||||
|
||||
def locate(s, E, jump, stay):
|
||||
"""porta in cima allo stack il contenitore che riceve il campo E. Ritorna 'elem' o 'field'."""
|
||||
f = s.fields[E]
|
||||
hdr = s.peek() in (0xf8, 0xf9)
|
||||
top = s.stack[-1]
|
||||
if not hdr and f['t'] == 0:
|
||||
for i in range(len(s.stack) - 1, -1, -1):
|
||||
c = s.stack[i]
|
||||
if c['kind'] == 'array' and c['occ'] == E:
|
||||
del s.stack[i + 1:]
|
||||
return 'elem'
|
||||
par = f['parent']
|
||||
i = s.owner_index(E, not jump)
|
||||
if i is None and jump and top['kind'] == 'struct' and top['node']:
|
||||
# variante di unione riusata in un altro contenitore: resta dove siamo
|
||||
f['own'].add(top['schema'])
|
||||
s.lenient += 1
|
||||
return 'field'
|
||||
fresh = top['kind'] == 'struct' and not top['node']
|
||||
if jump and fresh and i != len(s.stack) - 1:
|
||||
s.alias[top['field']] = par
|
||||
top['schema'] = par
|
||||
return 'field'
|
||||
if i is not None:
|
||||
if jump and i != len(s.stack) - 1:
|
||||
s.lenient += 1
|
||||
del s.stack[i + 1:]
|
||||
return 'field'
|
||||
if jump and top['kind'] == 'array' and not top['node'] and f['t'] == 0 and not hdr:
|
||||
top['occ'] = E
|
||||
return 'elem'
|
||||
raise Err(f"field {E} {f['name']}: container {par} not on stack @{s.p:x}")
|
||||
|
||||
def element(s, E):
|
||||
c = s.stack[-1]
|
||||
s.trans(E)
|
||||
s.lastc = c
|
||||
if s.peek() == 0xfb:
|
||||
s.p += 1
|
||||
b = s.u8()
|
||||
if len(c['node']) >= c['n']:
|
||||
raise Err(f"too many elements in {s.fields[c['field']]['name']} @{s.p:x}")
|
||||
if b == 0xe1:
|
||||
c['node'].append(None)
|
||||
elif b in (0xe2, 0xe3):
|
||||
v = Node()
|
||||
c['node'].append(v)
|
||||
s.stack.append(dict(kind='struct', field=c['field'], schema=s.sch(c['occ']), node=v, elem=True, occ=None, occ0=c['occ']))
|
||||
else:
|
||||
raise Err(f"array elem tag {b:02x} @{s.p - 1:x} in {s.fields[c['field']]['name']}")
|
||||
|
||||
def value(s, fid):
|
||||
c = s.stack[-1] if s.stack else None
|
||||
f = s.fields[fid]
|
||||
s.trans(fid)
|
||||
s.lastc = c
|
||||
if f['t'] != 0:
|
||||
v = s.scalar(f)
|
||||
else:
|
||||
b = s.u8()
|
||||
if b == 0xe1:
|
||||
v = None
|
||||
elif b in (0xe2, 0xe3):
|
||||
v = Node()
|
||||
elif b == 0xf8:
|
||||
v = Arr()
|
||||
n = s.rint()
|
||||
elif b == 0xf9:
|
||||
v = Arr()
|
||||
n = s.rint()
|
||||
s.rint()
|
||||
else:
|
||||
raise Err(f"ptr tag {b:02x} @{s.p - 1:x} field {f['name']}")
|
||||
if c is None:
|
||||
s.root = (f['name'], v)
|
||||
elif c['kind'] == 'array':
|
||||
c.setdefault('tail', []).append((f['name'], v))
|
||||
else:
|
||||
c['node'].append((f['name'], v))
|
||||
if isinstance(v, Arr):
|
||||
s.stack.append(dict(kind='array', field=fid, occ=fid, n=n, node=v))
|
||||
elif isinstance(v, Node):
|
||||
s.stack.append(dict(kind='struct', field=fid, schema=s.sch(fid), node=v, occ=None))
|
||||
|
||||
def run(s):
|
||||
b = s.b
|
||||
while s.p < len(b):
|
||||
m = s.marker()
|
||||
x = s.peek()
|
||||
if x is None:
|
||||
s.explicit(m or 0)
|
||||
break
|
||||
if x == 0xe0:
|
||||
s.p += 1
|
||||
t = s.u8()
|
||||
e = b.index(0, s.p)
|
||||
name = b[s.p:e].decode('latin1')
|
||||
s.p = e + 1
|
||||
m2 = s.marker()
|
||||
if m2 is not None:
|
||||
m = (m or 0) + m2
|
||||
s.popped = None
|
||||
if m is None:
|
||||
s.default_close()
|
||||
else:
|
||||
s.explicit(m)
|
||||
c = s.stack[-1] if s.stack else None
|
||||
s.nid += 1
|
||||
fid = s.nid
|
||||
if c is None:
|
||||
par = 0
|
||||
elif c['kind'] == 'array':
|
||||
par = ('A', c['occ'])
|
||||
else:
|
||||
par = c['schema']
|
||||
s.fields[fid] = dict(name=name, t=t, parent=par, own={par})
|
||||
pp = s.popped
|
||||
if pp is not None and c is not None and pp['kind'] == 'struct' and c['kind'] == 'struct' and pp['schema'] == c['schema'] and t == 0:
|
||||
s.fields[fid]['post'] = True
|
||||
if s.trace:
|
||||
print(f"{s.p:6x} {' ' * len(s.stack)}#{fid} {name} t={t}")
|
||||
s.value(fid)
|
||||
continue
|
||||
if m:
|
||||
s.explicit(m)
|
||||
jump = False
|
||||
if x == 0xf7:
|
||||
s.p += 1
|
||||
E = s.rint()
|
||||
jump = True
|
||||
if m is None:
|
||||
s.default_close()
|
||||
if E not in s.fields:
|
||||
raise Err(f"jump to unknown field {E} @{s.p:x}")
|
||||
else:
|
||||
E = s.next.get(s.last)
|
||||
if E is None:
|
||||
raise Err(f"no expected field @{s.p:x} after {s.last} "
|
||||
f"{s.fields[s.last]['name'] if s.last else ''}")
|
||||
kind = s.locate(E, jump or m is not None, m == 0)
|
||||
if s.trace:
|
||||
print(f"{s.p:6x} {' ' * len(s.stack)}{'>' if jump else '='}{E} {s.fields[E]['name']} {kind}")
|
||||
if kind == 'elem':
|
||||
s.element(E)
|
||||
else:
|
||||
s.value(E)
|
||||
return s.root
|
||||
|
||||
|
||||
def short(x, maxarr=40):
|
||||
if isinstance(x, Unk):
|
||||
return repr(x)
|
||||
if isinstance(x, float):
|
||||
return repr(round(x, 9))
|
||||
if isinstance(x, list):
|
||||
r = ', '.join(short(y) for y in x[:maxarr])
|
||||
return f"[{r}{', ...' if len(x) > maxarr else ''}] (n={len(x)})"
|
||||
return repr(x)
|
||||
|
||||
|
||||
def fmt(v, ind=0, out=None, maxarr=40):
|
||||
pad = ' ' * ind
|
||||
if isinstance(v, Node):
|
||||
for n, x in v:
|
||||
if isinstance(x, (Node, Arr)):
|
||||
out.append(f"{pad}{n}:")
|
||||
fmt(x, ind + 1, out, maxarr)
|
||||
else:
|
||||
out.append(f"{pad}{n} = {short(x, maxarr)}")
|
||||
elif isinstance(v, Arr):
|
||||
for i, x in enumerate(v):
|
||||
out.append(f"{pad}[{i}]")
|
||||
fmt(x, ind + 1, out, maxarr)
|
||||
|
||||
|
||||
def parse(b, trace=False):
|
||||
p = P(b, trace)
|
||||
try:
|
||||
root = p.run()
|
||||
return p, root, None
|
||||
except (Err, IndexError, ValueError, KeyError) as e:
|
||||
return p, p.root, e
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
import prt
|
||||
d, secs, order = prt.load(sys.argv[1])
|
||||
names = sys.argv[2].split(',') if len(sys.argv) > 2 and sys.argv[2] != '*' else [n for n in order if secs[n][2][2] == '9']
|
||||
outdir = sys.argv[3] if len(sys.argv) > 3 and sys.argv[3] != '-' else None
|
||||
for nme in names:
|
||||
o, l, f = secs[nme]
|
||||
b = d[o:o + l]
|
||||
p, root, e = parse(b, trace=len(sys.argv) > 4)
|
||||
ok = e is None and not p.stack
|
||||
print(f"{nme:22} len={l:7} parsed={p.p:7} fields={p.nid:5} lenient={p.lenient} {'OK' if ok else 'ERR ' + repr(e) + ' stack=' + str(len(p.stack))}")
|
||||
if e is not None:
|
||||
prt.hexdump(b, max(0, p.p - 64), 128)
|
||||
if outdir and root:
|
||||
out = [root[0] + ':']
|
||||
fmt(root[1], 1, out)
|
||||
open(f"{outdir}/{nme.replace(':', '_').replace('#', '_')}.txt", 'w', encoding='utf-8').write('\n'.join(out))
|
||||
@@ -0,0 +1,47 @@
|
||||
import sys,os,math,collections,struct,glob,itertools
|
||||
sys.path.insert(0,os.path.dirname(os.path.abspath(__file__)))
|
||||
import prt,psb
|
||||
class TF(float):
|
||||
pass
|
||||
orig=psb.P.rdbl
|
||||
def rdbl(s):
|
||||
p0=s.p; t=s.b[p0]
|
||||
v=orig(s)
|
||||
if 0x49<=t<=0xdf and not isinstance(v,psb.Unk):
|
||||
o=TF(v); o.tag=t; o.pl=bytes(s.b[p0+1:p0+7]); return o
|
||||
return v
|
||||
psb.P.rdbl=rdbl
|
||||
OFFS=[0]+list(range(0x44,0x4d))
|
||||
def mag(tf,off):
|
||||
return struct.unpack('>d',(0x3f75+tf.tag-off).to_bytes(2,'big')+tf.pl)[0]
|
||||
votes=collections.defaultdict(collections.Counter)
|
||||
def walk(v,name=''):
|
||||
if isinstance(v,psb.Node):
|
||||
for n,x in v: walk(x,n)
|
||||
elif isinstance(v,psb.Arr):
|
||||
for x in v: walk(x,name)
|
||||
elif isinstance(v,list) and v and isinstance(v[0],list) and len(v) in (3,4) and len(v[0])==3 and name in ('local_sys','orient','matrix','transf_to_local'):
|
||||
rows=v[:3]
|
||||
if any(isinstance(c,psb.Unk) for r in rows for c in r): return
|
||||
vecs=rows+[[rows[i][j] for i in range(3)] for j in range(3)]
|
||||
for r in vecs:
|
||||
tf=[c for c in r if isinstance(c,TF)]
|
||||
if not tf or len(tf)>2: continue
|
||||
rest=sum(c*c for c in r if not isinstance(c,TF))
|
||||
sols=[]
|
||||
for offs in itertools.product(OFFS,repeat=len(tf)):
|
||||
n=rest+sum(mag(c,o)**2 for c,o in zip(tf,offs))
|
||||
if abs(n-1)<1e-9: sols.append(offs)
|
||||
if len(sols)==1:
|
||||
for c,o in zip(tf,sols[0]): votes[c.tag][o]+=1
|
||||
for f in sys.argv[1:]:
|
||||
for g in glob.glob(f):
|
||||
try: d,secs,order=prt.load(g)
|
||||
except Exception: continue
|
||||
for nme in order:
|
||||
o,l,fl=secs[nme]
|
||||
if fl[2]!='9' or l<200: continue
|
||||
p,root,e=psb.parse(d[o:o+l])
|
||||
if root: walk(root[1])
|
||||
for t in sorted(votes):
|
||||
print(hex(t), {hex(o):n for o,n in votes[t].items()}, ' -> prefix', [hex((0x3f75+t-o) | (0x8000 if o else 0)) for o in votes[t]])
|
||||
@@ -0,0 +1,75 @@
|
||||
"""Decompressore Unix compress (.Z, LZW) minimale, come ncompress."""
|
||||
import sys, os
|
||||
|
||||
|
||||
def unz(data):
|
||||
assert data[0] == 0x1f and data[1] == 0x9d, "non e' un flusso .Z"
|
||||
flags = data[2]
|
||||
maxbits = flags & 0x1f
|
||||
block = bool(flags & 0x80)
|
||||
out = bytearray()
|
||||
n_bits = 9
|
||||
maxcode = (1 << n_bits) - 1
|
||||
table = {i: bytes([i]) for i in range(256)}
|
||||
free = 257 if block else 256
|
||||
pos = 3 * 8 # posizione in bit
|
||||
total = len(data) * 8
|
||||
start = pos # inizio del gruppo corrente di codici (per il riallineamento)
|
||||
old = None
|
||||
while pos + n_bits <= total:
|
||||
byte = pos >> 3
|
||||
chunk = int.from_bytes(data[byte:byte + 4], 'little')
|
||||
code = (chunk >> (pos & 7)) & ((1 << n_bits) - 1)
|
||||
pos += n_bits
|
||||
if old is None:
|
||||
old = table[code]
|
||||
out += old
|
||||
continue
|
||||
if code == 256 and block:
|
||||
table = {i: bytes([i]) for i in range(256)}
|
||||
free = 256
|
||||
# riallinea a multipli di 8 codici
|
||||
grp = n_bits * 8
|
||||
pos = start + ((pos - start + grp - 1) // grp) * grp
|
||||
n_bits = 9
|
||||
maxcode = (1 << n_bits) - 1
|
||||
start = pos
|
||||
old = None
|
||||
free = 257
|
||||
continue
|
||||
if code in table:
|
||||
entry = table[code]
|
||||
elif code == free:
|
||||
entry = old + old[:1]
|
||||
else:
|
||||
raise ValueError(f"codice LZW non valido {code} (free={free}) a bit {pos}")
|
||||
out += entry
|
||||
if free <= (1 << maxbits) - 1:
|
||||
table[free] = old + entry[:1]
|
||||
free += 1
|
||||
old = entry
|
||||
if free > maxcode and n_bits < maxbits:
|
||||
grp = n_bits * 8
|
||||
pos = start + ((pos - start + grp - 1) // grp) * grp
|
||||
n_bits += 1
|
||||
maxcode = (1 << n_bits) - 1
|
||||
start = pos
|
||||
return bytes(out)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
import prt
|
||||
d, secs, order = prt.load(sys.argv[1])
|
||||
outdir = sys.argv[2]
|
||||
for n in order:
|
||||
o, l, f = secs[n]
|
||||
b = d[o:o + l]
|
||||
if b[:2] == b'\x1f\x9d':
|
||||
try:
|
||||
u = unz(b)
|
||||
except Exception as e:
|
||||
print(n, 'ERR', e)
|
||||
continue
|
||||
print(n, 'compressa', l, '->', len(u), 'dichiarata', int(f[0], 16), u[:60])
|
||||
open(os.path.join(outdir, n.replace(':', '_') + '.bin'), 'wb').write(u)
|
||||
Reference in new issue
Block a user