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 # Profils de remplacement : selection des anciens et nouveaux noeuds, correspondance des broches. # Usage : DiffRemplacementNoeuds.py avant.txt apres.txt [L7a|L7b|CX-B12] (L7a par defaut) PROFILS={ 'L7a':{'old':('K2Node_VariableGet',lambda l:'Engine.SkinnedMeshComponent\'",MemberName="SkeletalMesh"' in l), 'new':('K2Node_CallFunction',lambda l:'MemberName="GetSkeletalMesh"' in l and 'LPMeshCompat' in l), 'pins':[('self','Component'),('SkeletalMesh','ReturnValue')]}, 'L7b':{'old':('K2Node_CallFunction',lambda l:'MemberName="K2_GetRandomPointInNavigableRadius"' in l), 'new':('K2Node_CallFunction',lambda l:'MemberName="GetRandomLocationInNavigableRadius"' in l and 'LPNavigationHelpers' in l), 'pins':[(x,x) for x in ('WorldContextObject','Origin','RandomLocation','Radius','NavData','FilterClass','ReturnValue')]}, 'CX-B12':{'old':('K2Node_CallFunction',lambda l:'KismetStringLibrary' in l and re.search(r'MemberName="(Contains|FindSubstring|Split)"',l) is not None), 'new':('K2Node_CallFunction',lambda l:'LPStringCompat' in l and re.search(r'MemberName="(Contains|FindSubstring|Split)"',l) is not None), 'pins':[(x,x) for x in ('SearchIn','Substring','bUseCase','bSearchFromEnd','StartPosition','SourceString','InStr','LeftS','RightS','SearchCase','SearchDir','ReturnValue')]}, } PROFIL=PROFILS[sys.argv[3] if len(sys.argv)>3 else 'L7a'] 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 def select(D,sel): return {k:v for k,v in D.items() if v['cls']==sel[0] and not iscopy(k[1]) and any(sel[1](l) for l in v['lines'])} old=select(A,PROFIL['old']); new=select(B,PROFIL['new']) 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: for op,np_ in PROFIL['pins']: l=l.replace(o+'.'+op,n+'.'+np_) 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(3).split(', ') if x) return None def pinhead(o,name): # Categorie et valeurs par defaut de la broche (sans les liaisons). for l in o['lines']: m=re.match(r'^ pin (\S+) (\[.*\] defaut=.*) -> (.*)$',l) if m and m.group(1)==name: return m.group(2) 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) # Broche absente des deux cotes (profil multi-fonctions : Split n'a pas SearchIn...) : egalite. same=all((pinlinks(v,op) is None and pinlinks(n,np_) is None) or rn(pinlinks(v,op))==pinlinks(n,np_) for op,np_ in PROFIL['pins']) # Profil a broches homonymes : les valeurs par defaut doivent aussi etre reportees. if same and all(op==np_ for op,np_ in PROFIL['pins']): same=all(pinhead(v,op)==pinhead(n,op) for op,_ in PROFIL['pins']) if same: ok+=1 else: bad+=1; print('ECART',k[1],[(op,pinhead(v,op),rn(pinlinks(v,op)),pinhead(n,np_),pinlinks(n,np_)) for op,np_ in PROFIL['pins']]) print('noeuds remplaces : liaisons identiques',ok,'/ ecarts',bad)