Files
agentmc/scaffold/pg2srg.py
JianFeeeee e223be2571 P8: scaffold —— 反混淆 + MC 渲染栈离屏初始化 + 真渲染出图
实测链路(全绿):
  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 同格式)⇒ 这就是数据喂渲染器的落点。
2026-09-25 15:39:41 +08:00

105 lines
4.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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])