MCP 反混淆转换器 MCPDeobfuscationTransformer
基本信息
| 属性 | 值 |
|---|---|
| coremod 入口 | com.myname.mymodid.asm.MyModLoadingPlugin |
| 声明位置 | gradle.properties:122 coreModClass = asm.MyModLoadingPlugin |
| 转换器实现 | com.myname.mymodid.asm.MCPDeobfuscationTransformer |
| 映射加载器 | com.myname.mymodid.asm.MCPRemapper |
| 生效范围 | 仅本模组自己的类(前缀 com.myname.mymodid.) |
| 映射资源 | /conf/mcp-srg.srg、/conf/methods.csv、/conf/fields.csv |
| 静态自检 | ./gradlew runMCPDeobfuscationTester |
功能
这是本示例模组技术上最实质的一部分:一个 core mod(coremod),在游戏启动的类加载阶段把模组自身字节码里引用的 MCP 字段/方法名改写为 1.7.10 运行时真正存在的 Searge 名。
之所以需要它:ESPRenderer 直接书写 Tessellator.instance、Entity.posX、Blocks.air、World.getBlock 这类可读的 MCP 名称(ESPRenderer.java:1-8 的文件头注释说明了这一取舍)。这样写代码很舒服,但打包后的字节码里字段名是 field_78409_u 这类 SRG 名,与 MCP 名不一致,运行时就会 NoSuchFieldError。本转换器在类加载时补上这一步,思路与 NEI 借 CodeChickenCore 的 MCPDeobfuscationTransformer 完全一致(ESPRenderer.java:6-8、MCPDeobfuscationTransformer.java:1-4)。
注册链路
gradle.properties:122声明coreModClass = asm.MyModLoadingPlugin;配合modGroup = com.myname.mymodid(gradle.properties:14)解析为com.myname.mymodid.asm.MyModLoadingPlugin- 该类作为
FMLCorePlugin写入 jar 的 MANIFEST(MyModLoadingPlugin.java:1-4注释所述;gradle.properties:119-121说明该属性是 core mod 的兼容写法) getASMTransformerClass()返回MCPDeobfuscationTransformer(MyModLoadingPlugin.java:15-17)getModContainerClass()返回null(MyModLoadingPlugin.java:22-23)——真正的 mod 容器仍是有@Mod注解的MyMod,coremod 只负责提供 ASM 转换器;getSetupClass()与getAccessTransformerClass()同样返回null(MyModLoadingPlugin.java:27-29, 37-39),injectData为空实现(MyModLoadingPlugin.java:32-34)
关于 MANIFEST 的具体写法:仓库内
build.gradle.kts只有 13 行,主体是id("com.gtnewhorizons.gtnhconvention"),MANIFEST 的FMLCorePlugin属性由该约定插件依据coreModClass生成。插件实现不在本仓库内,其具体生成逻辑无法从本仓库源码核实。
转换范围
shouldRewrite 仅对 com.myname.mymodid. 与 com/myname/mymodid/ 开头的类名返回 true(MCPDeobfuscationTransformer.java:33-38)。其他类一律原样返回(MCPDeobfuscationTransformer.java:44)——net/minecraft/* 由 Forge 自带的 DeobfuscationTransformer 负责,源码注释明确说明了这个分工(MCPDeobfuscationTransformer.java:3-4)。
转换通过 RemappingClassAdapter 完成,写字节码时用 ClassWriter.COMPUTE_MAXS 配 ClassReader.EXPAND_FRAMES(MCPDeobfuscationTransformer.java:99-101)。整个过程包在 try/catch(Throwable) 中,失败时打印错误并返回未转换的原始字节码(MCPDeobfuscationTransformer.java:113-115),即失败会退化为「保持原样」而非崩溃。首个被成功改写的类会打印一行 [MyModASM] Activated: ... 日志,含父类链与映射条目数(MCPDeobfuscationTransformer.java:103-111)。
映射来源与查表策略
MCPRemapper 构造时加载三份资源(MCPRemapper.java:32-34, 44-48):
| 资源 | 作用 | 缺失时 |
|---|---|---|
/conf/mcp-srg.srg |
带 owner 信息的 MD:/FD: 行,权威来源 |
抛 IllegalStateException(MCPRemapper.java:56-60) |
/conf/methods.csv |
searge,mcp,side,desc 扁平表,方法名兜底 |
打印警告并跳过(MCPRemapper.java:105-108) |
/conf/fields.csv |
同上,字段名兜底 | 打印警告并跳过(MCPRemapper.java:105-108) |
查表分两套键(MCPRemapper.java:37-42):
- 方法用
owner#name#desc作键(methodMap),只有 owner 精确匹配才命中;查不到再退到仅按名字查的methodFallback(MCPRemapper.java:140-146)。理由写在MCPRemapper.java:13-18:同一个 MCP 方法名在不同 owner 下可能对应不同 Searge 方法,所以方法名不能只按名字查。 - 字段只用名字查(
fieldMap,MCPRemapper.java:149-154),理由是 1.7.10 中 Searge 字段名全局唯一(MCPRemapper.java:14-15)。
CSV 载入时只填补 srg 中不存在的条目(MCPRemapper.java:127-129),保证 srg 优先。
父类链回退
因为 javac 生成的 getfield/invokevirtual 以接收者的声明类型作为 owner(EntityPlayer.posX 而非声明于 Entity 的 posX,MCPDeobfuscationTransformer.java:6-8),转换器为每个类构造一条 Remapper:先按字面 owner 查,未命中再依次按当前类的直接父类、祖父类重查(MCPDeobfuscationTransformer.java:55-96)。父类名通过 Class.forName 反射取得,失败返回 null(MCPDeobfuscationTransformer.java:127-137)。相对 CodeChickenCore 的完整继承链求值,这里只回退 1–2 跳(MCPDeobfuscationTransformer.java:9-13 承认这是简化)。
数值
| 数值名 | 值 | 来源 |
|---|---|---|
| 父类回退跳数 | 最多 2 跳(super1、super2) | MCPDeobfuscationTransformer.java:55-59 |
| srg 资源缺失行为 | 抛 IllegalStateException(启动失败) |
MCPRemapper.java:57-59 |
| CSV 资源缺失行为 | 警告后跳过 | MCPRemapper.java:106-108 |
| 转换失败行为 | 返回原始字节码 | MCPDeobfuscationTransformer.java:114-115 |
| 映射条目总数 | 无法核实:取决于 mcp-srg.srg 实际行数与两者去重后的结果,需运行时看 [MyModASM] Activated 日志 |
— |
静态自检
src/test/java/com/myname/mymodid/asm/MCPDeobfuscationTester.java 是一个 main 方法,通过 Gradle 任务 runMCPDeobfuscationTester 运行(该任务注册于 build.gradle.kts:8-13,依赖 compileTestJava,主类为 MCPDeobfuscationTester)。
它取出刚编译出的 com.myname.mymodid.client.ESPRenderer 字节码,走一遍与 LaunchWrapper 相同的 transform 调用(MCPDeobfuscationTester.java:51-52),再用 ASM 树 API 逐条检查字段/方法引用是否已带 field_ / func_ 前缀(MCPDeobfuscationTester.java:66-90)。
关键点:它主动排除 fml_at.cfg / forge_at.cfg 已开放为 public 的类(FMLClientHandler、FMLCommonHandler、ClientRegistry、ForgeHooksClient、MinecraftForge,见 MCPDeobfuscationTester.java:115-121),只把 net/minecraft/ 与 cpw/mods/ 下未改写的引用算作失败(MCPDeobfuscationTester.java:123-134);还有 this$、含 $ 的内部类字段与 <init> 等构造器方法被跳过(MCPDeobfuscationTester.java:74, 84)。有残留则以退出码 2 失败(MCPDeobfuscationTester.java:106-109)。
该自检的执行结果无法从源码核实:仓库内
logs/latest.log为空文件(0 行),没有任何[MyModASM]或MCPDeobfuscationTester的历史输出。
已知问题
mapFieldName 的父类回退分支是无效代码。 MCPDeobfuscationTransformer.java:69-78 连续三次用完全相同的 REMAPPER.fieldMap.get(name) 查表,仅判断条件不同(s1 != null、s2 != null)。由于 fieldMap 仅以名字为键(MCPRemapper.java:42),同一个 name 三次查询结果必然相同,第一个 if (v != null) return v;(MCPDeobfuscationTransformer.java:70)已经覆盖全部情况,后两个分支永远不会产生新的映射结果。其上方的注释(MCPDeobfuscationTransformer.java:66-68)声称"再沿父类链行走",与实际行为不符。
相比之下 mapMethodName 的回退分支(MCPDeobfuscationTransformer.java:87-94)确实有效——方法表是 owner 敏感的,把 owner 换成父类能命中新条目。
相关条目
- 矿石与刷怪笼高亮 ESP - 本转换器的主要使用方