实测链路(全绿): 1. 官方 ProGuard 映射 → SRG → SpecialSource 重映射 → client-named.jar 2. 反编译确认 MC 渲染器挂在 Minecraft 单例上,不能独立 new 3. 权威序列:tryDetectVersion → Bootstrap → RenderSystem → new Minecraft(cfg) 4. 渲 3 帧 + glReadPixels → PNG,画面是 Mojang 加载屏(真渲染,非清屏) 三个坑(实测证据): - 漏 tryDetectVersion → Game version not set - FolderData 参数顺序错 → icons/ 404 - assetIndex 用 id(5) 而非落盘名(1.20.1) → 资源全找不到 下一步:ClientLevel 构造只需 listener.registryAccess(), ClientChunkCache.replaceWithPacketData 接收 map_chunk 原始字节 (与 chunk.py 同格式)⇒ 这就是数据喂渲染器的落点。
105 lines
4.2 KiB
Python
105 lines
4.2 KiB
Python
#!/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])
|