LifePlay-Revival/Tools/DiffRemplacementNoeuds.py
edujiro5972 5d1f9a142f L7a (CX-B10) : 56 noeuds Get SkeletalMesh deprecies remplaces par LPMeshCompat::GetSkeletalMesh (R-03, ERR-012)
Semantique exacte du getter moteur (Cast<USkeletalMesh>(GetSkinnedAsset())) pour tout
USkinnedMeshComponent, contrairement a CX-B05. Diff avant/apres : 56/56 liaisons identiques,
0 autre objet modifie. Compilation 190 -> 6 avertissements, cuisson 95 -> 3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 17:05:46 +02:00

96 lines
4.9 KiB
Python

import re,sys,collections
def parse(p):
objs={}; pkg=None; key=None
for line in open(p,encoding='utf-8'):
line=line.rstrip('\n')
if line.startswith('##### '): pkg=line[6:]; continue
m=re.match(r'^ (\S+) \[(\w+)\]$',line)
if m: key=(pkg,m.group(1)); objs[key]={'cls':m.group(2),'lines':[]}; continue
if key: objs[key]['lines'].append(line)
return objs
A=parse(sys.argv[1]); B=parse(sys.argv[2])
def graph(path): return path.split(':')[-1].split('.')[0] if ':' in path else ''
def iscopy(path):
g=graph(path); return "ExecuteUbergraph" in g or g.endswith("_MERGED") or re.fullmatch(r"EdGraph_[0-9]+",g) is not None
def pos(o):
x=y='0'
for l in o['lines']:
if l.startswith(' NodePosX = '): x=l.split('= ')[1]
if l.startswith(' NodePosY = '): y=l.split('= ')[1]
return x,y
old={k:v for k,v in A.items() if v['cls']=='K2Node_VariableGet' and not iscopy(k[1]) and any('Engine.SkinnedMeshComponent\'",MemberName="SkeletalMesh"' in l for l in v['lines'])}
new={k:v for k,v in B.items() if v['cls']=='K2Node_CallFunction' and not iscopy(k[1]) and any('MemberName="GetSkeletalMesh"' in l and 'LPMeshCompat' in l for l in v['lines'])}
print('anciens noeuds',len(old),'nouveaux',len(new))
idx={(k[0],k[1].rsplit('.',1)[0],pos(v)):k for k,v in new.items()}
ren={}; miss=0
for k,v in old.items():
t=idx.get((k[0],k[1].rsplit('.',1)[0],pos(v)))
if not t: miss+=1; continue
ren[(k[0],graph(k[1]),k[1].rsplit('.',1)[1])]=t[1].rsplit('.',1)[1]
print('apparies',len(ren),'non apparies',miss)
skip=re.compile(r'^ (NodeGuid|GraphGuid|ErrorType|ErrorMsg|bHasCompilerMessage|NodePosX|NodePosY) = ')
def norm(pkg,g,lines):
out=[]
for l in lines:
if skip.match(l): continue
for (p,gg,o),n in ren.items():
if p==pkg and gg==g:
l=l.replace(o+'.SkeletalMesh',n+'.ReturnValue').replace(o+'.self',n+'.Component')
out.append(canon(l,pkg,g))
return out
def canon(l,pkg=None,g=None):
if l.startswith(' pin ') and ' -> ' in l:
head,links=l.rsplit(' -> ',1)
return head+' -> '+', '.join(sorted(x for x in links.split(', ') if x))
if l.startswith(' Nodes = ('):
items=[x.strip('"') for x in l[len(' Nodes = ('):-1].split('","')]
if pkg is not None:
items=[x for x in items if not any(pkg==p2 and ("."+o+"'") in x and (":"+gg+".") in x for (p2,gg,o) in ren)]
items+=[ "NOUVEAU:"+pkg+":"+gg+"."+n for (p2,gg,o),n in ren.items() if p2==pkg and gg==g ]
else:
items=[("NOUVEAU:"+x.split("'/Game/")[0] if False else x) for x in items]
return ' Nodes = SET'+str(sorted(items))
return l
newnames={(k[0],graph(k[1]),k[1].rsplit('.',1)[1]) for k in new}
def canonB(k,l):
if l.startswith(' Nodes = ('):
items=[x.strip('"') for x in l[len(' Nodes = ('):-1].split('","')]
keep=[]; add=[]
for x in items:
nm=x.rsplit('.',1)[1].rstrip("'")
if (k[0],k[1].split(':')[-1],nm) in newnames: add.append("NOUVEAU:"+k[0]+":"+k[1].split(':')[-1]+"."+nm)
else: keep.append(x)
return ' Nodes = SET'+str(sorted(keep+add))
return canon(l)
diffs=collections.Counter(); ex=[]
for k in set(A)|set(B):
if iscopy(k[1]) or k in old or k in new: continue
if k[1].startswith('PLACEHOLDER'): continue
a=norm(k[0],graph(k[1]),A[k]['lines']) if k in A else None; b=[canonB(k,l) for l in B[k]['lines'] if not skip.match(l)] if k in B else None
if a!=b:
cls=(A.get(k) or B.get(k))['cls']
diffs[(k[0],cls,'absent avant' if a is None else 'absent apres' if b is None else 'modifie')]+=1
if len(ex)<40: ex.append((k,[x for x in (a or []) if x not in (b or [])][:2],[x for x in (b or []) if x not in (a or [])][:2]))
print('objets en ecart hors noeuds remplaces et copies :',sum(diffs.values()))
for k,v in sorted(diffs.items()): print(' ',v,*k)
for e in ex: print(e[0]); [print(' -',x[:160]) for x in e[1]]; [print(' +',x[:160]) for x in e[2]]
def pinlinks(o,name):
for l in o['lines']:
m=re.match(r'^ pin (\S+) \[.*\] defaut=.* -> (.*)$',l)
if m and m.group(1)==name: return sorted(x for x in m.group(2).split(', ') if x)
return None
ok=bad=0
for k,v in old.items():
t=idx[(k[0],k[1].rsplit('.',1)[0],pos(v))]; n=B[t]
g=graph(k[1])
def rn(lst):
out=[]
for x in lst or []:
for (p2,gg,o),nn in ren.items():
if p2==k[0] and gg==g: x=x.replace(o+'.',nn+'.') if x.split('.')[0]==o else x
out.append(x)
return sorted(out)
if rn(pinlinks(v,'self'))==pinlinks(n,'Component') and rn(pinlinks(v,'SkeletalMesh'))==pinlinks(n,'ReturnValue'): ok+=1
else:
bad+=1; print('ECART',k[1],pinlinks(v,'self'),pinlinks(n,'Component'),pinlinks(v,'SkeletalMesh'),pinlinks(n,'ReturnValue'))
print('noeuds remplaces : liaisons identiques',ok,'/ ecarts',bad)