package com.spineview.deobf; import java.io.BufferedWriter; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.commons.ClassRemapper; import org.objectweb.asm.commons.Remapper; /** * 按"名字映射表"重命名载荷里的**成员**(字段/方法),产出一份新的载荷 zip, * 专门用来反编译 —— 这样反编译出来的代码里,声明和直接调用都带可识别的名字。 * * ⚠️ 只改成员名,**不改类名**:载荷的 invokedynamic 是在运行时用加密串解出 * "类名 + 方法名 + 描述符"再反射查找的,类名必须保持不变(成员名改了以后这份 zip * 只用于反编译,不能拿去运行)。 * * 映射表(TSV,4 列,`#` 开头是注释): * ownernamedescnewName * - owner:内部类名,如 `et`、`com/badlogic/gdx/scenes/scene2d/ui/Tree` * - name :混淆成员名(字段或方法) * - desc :描述符;字段如 `F`(float)、`Ljava/lang/String;`; * 方法如 `(Ldl;)LdR;`。**写 `*` 表示"该 owner 下所有同名成员"**(仅在 * 确定这一层不会撞车时用) * - newName:要换成的新名字 * * 用法: * java -cp "asm.jar:asm-tree.jar:asm-commons.jar:build" com.spineview.deobf.Renamer */ public final class Renamer { public static void main(String[] args) throws Exception { Path in = Paths.get(args[0]); Path map = Paths.get(args[1]); Path out = Paths.get(args[2]); Map exact = new HashMap(); Map wildcard = new HashMap(); int loaded = 0; for (String line : Files.readAllLines(map, StandardCharsets.UTF_8)) { if (line.isEmpty() || line.startsWith("#")) continue; String[] parts = line.split("\t"); if (parts.length < 4 || parts[0].isEmpty() || parts[1].isEmpty()) continue; String owner = parts[0]; String name = parts[1]; String desc = parts[2]; String renamed = parts[3]; if (renamed.isEmpty()) continue; if (desc.equals("*")) { wildcard.put(owner + "#" + name, renamed); } else { exact.put(owner + "#" + name + "#" + desc, renamed); } loaded++; } System.err.println("映射条目 " + loaded + "(具体 " + exact.size() + ",通配 " + wildcard.size() + ")"); int classes = 0; int renamedMembers = 0; try (ZipFile zip = new ZipFile(in.toFile()); ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(out))) { Enumeration entries = zip.entries(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); byte[] raw = readAll(zip.getInputStream(entry)); if (!entry.getName().endsWith(".class")) { zipOut.putNextEntry(new ZipEntry(entry.getName())); zipOut.write(raw); zipOut.closeEntry(); continue; } try { ClassReader reader = new ClassReader(raw); ClassWriter writer = new ClassWriter(0); final Map exactMap = exact; final Map wildcardMap = wildcard; final int[] counter = new int[1]; Remapper remapper = new Remapper() { @Override public String mapFieldName(String owner, String name, String descriptor) { String renamed = lookup(owner, name, descriptor, exactMap, wildcardMap); if (renamed == null) return name; counter[0]++; return renamed; } @Override public String mapMethodName(String owner, String name, String descriptor) { String renamed = lookup(owner, name, descriptor, exactMap, wildcardMap); if (renamed == null) return name; counter[0]++; return renamed; } }; reader.accept(new ClassRemapper(writer, remapper), 0); byte[] rewritten = writer.toByteArray(); zipOut.putNextEntry(new ZipEntry(entry.getName())); zipOut.write(rewritten); zipOut.closeEntry(); classes++; renamedMembers += counter[0]; } catch (Throwable error) { zipOut.putNextEntry(new ZipEntry(entry.getName())); zipOut.write(raw); zipOut.closeEntry(); } } } System.err.println("处理 " + classes + " 个类,改名成员引用 " + renamedMembers + " 处 -> " + out); } private static String lookup(String owner, String name, String descriptor, Map exact, Map wildcard) { String found = exact.get(owner + "#" + name + "#" + descriptor); if (found != null) return found; return wildcard.get(owner + "#" + name); } private static byte[] readAll(InputStream in) throws java.io.IOException { ByteArrayOutputStream buffer = new ByteArrayOutputStream(); byte[] chunk = new byte[65536]; int n; while ((n = in.read(chunk)) > 0) buffer.write(chunk, 0, n); return buffer.toByteArray(); } /** 生成"没被用到"的映射条目清单,方便发现写错的 owner/desc。 */ static List unused(Map hits) { List out = new java.util.ArrayList(); for (Map.Entry entry : hits.entrySet()) { if (entry.getValue() == 0) out.add(entry.getKey()); } return out; } static void writeReport(Path path, Map hits) throws Exception { try (BufferedWriter writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) { for (Map.Entry entry : hits.entrySet()) { writer.write(entry.getValue() + "\t" + entry.getKey()); writer.write('\n'); } } } }