#!/usr/bin/env python3 """ProGuard mappings (Mojang官方) -> SRG (SpecialSource 可读) ProGuard 格式: net.minecraft.client.Minecraft -> enn: 187:307:void render(...) -> a org.slf4j.Logger LOGGER -> c SRG 格式: CL: enn net/minecraft/client/Minecraft MD: enn/a (FJZ)V net/minecraft/client/Minecraft/render (FJZ)V FD: fjv/c net/minecraft/client/renderer/LevelRenderer/LOGGER """ import re, sys PRIM = {'void':'V','boolean':'Z','byte':'B','char':'C','short':'S', 'int':'I','long':'J','float':'F','double':'D'} def main(pg_path, out_path): classes = {} # named -> obf class_lines = [] # (obf, named) members = [] # ('MD'|'FD', obfOwner, obfName, namedOwner, namedName, [namedDesc]) cur_named = cur_obf = None class_re = re.compile(r'^(\S+) -> (\S+):$') meth_re = re.compile(r'^\s*\d+:\d+:(.*) -> (\S+)$') field_re = re.compile(r'^\s+(\S+)\s+(\S+) -> (\S+)$') with open(pg_path, encoding='utf-8') as f: for line in f: line = line.rstrip('\n') if not line or line.startswith('#'): continue m = class_re.match(line) if m: cur_named, cur_obf = m.group(1), m.group(2).rstrip(':') classes[cur_named] = cur_obf class_lines.append((cur_obf, cur_named)) continue if cur_obf is None: continue # ★ ProGuard 的方法行「可以没有」行号前缀(如 `Foo acquire() -> a`), # 所以判据是「-> 左侧以 ) 结尾」而不是「行首有 N:M:」。靠行号判会漏成字段。 m = re.match(r'^\s*(?:\d+:\d+:)?(.*?)\s+->\s+(\S+)$', line) if not m: continue sig, obf_name = m.group(1).strip(), m.group(2) if sig.endswith(')'): members.append(('MD', cur_obf, obf_name, cur_named, sig)) else: # 字段:"type name" parts = sig.rsplit(' ', 1) if len(parts) != 2: continue ftype, fname = parts[0], parts[1] members.append(('FD', cur_obf, obf_name, cur_named, ftype + ' ' + fname)) def to_obf_desc(t): """named 类型 -> obf JVM 描述符""" t = t.strip() if t in PRIM: return PRIM[t] if t.endswith('[]'): return '[' + to_obf_desc(t[:-2]) # 内部类在 ProGuard 里用 $ 写,obf 侧也是 $ 形式(已包含在 classes 里) obf = classes.get(t) if obf is None: # 可能是混淆后的内部类写法差异,或非 MC 类(java/*, netty 等)-> 转 / return 'L' + t.replace('.', '/') + ';' return 'L' + obf + ';' def split_params(s): """按顶层逗号切分(泛型里不会有逗号,ProGuard 已擦除泛型)""" if not s.strip(): return [] return [p.strip() for p in s.split(',')] with open(out_path, 'w', encoding='utf-8') as out: for obf, named in class_lines: out.write(f'CL: {obf} {named.replace(".", "/")}\n') for kind, obf_owner, obf_name, named_owner, payload in members: named_owner_s = named_owner.replace('.', '/') if kind == 'FD': ftype, fname = payload.split(' ', 1) out.write(f'FD: {obf_owner}/{obf_name} {named_owner_s}/{fname}\n') else: # payload = "retType name(args)" mm = re.match(r'^(.*?)\s+([\w$<>]+)\((.*)\)$', payload) if not mm: continue ret, name, argstr = mm.group(1), mm.group(2), mm.group(3) try: params = split_params(argstr) obf_desc = '(' + ''.join(to_obf_desc(p) for p in params) + ')' + to_obf_desc(ret) except Exception: continue out.write(f'MD: {obf_owner}/{obf_name} {obf_desc} ' f'{named_owner_s}/{name} {obf_desc}\n') sys.stderr.write(f'CL={len(class_lines)} members={len(members)}\n') if __name__ == '__main__': main(sys.argv[1], sys.argv[2])