Inspector 显示用映射表翻译真名:字段/方法显示为 realName(混淆名),表来自 tools/deobf/names.tsv(运行时载荷不能改名,indy 靠加密串里的原名反射查找)

This commit is contained in:
tianmo committed 2026-10-16 16:44:12 +08:00
1 parent 0cdc0894a4
commit f0b78dbfd6
1 file changed
+152 -10
+152 -10
View File
@@ -49,6 +49,8 @@ public final class ObjectPanel {
java.util.List<Object[]> inspectFields = new ArrayList<Object[]>();
List<Object> objects = new ArrayList<Object>();
Object target;
Object root;
Object content;
boolean scrollFocusLogged;
boolean geometryDumped;
Class<?> tableClass;
@@ -131,14 +133,21 @@ public final class ObjectPanel {
Object cell = call(root, Obf.TABLE_ADD, new Class<?>[] {actorClass}, cellClass, buttons);
layoutCell(cell, Obf.CELL_LEFT);
call(root, Obf.TABLE_ROW, new Class<?>[0], cellClass);
// 中段:列表(唯一 expand+fill 的行,吃掉剩余高度)
cell = call(root, Obf.TABLE_ADD, new Class<?>[] {actorClass}, cellClass, scroll);
layoutCell(cell, Obf.CELL_TOP, Obf.CELL_EXPAND, Obf.CELL_FILL);
state.listCell = cell;
// 下段:日志(独立一行,自然高度贴底;不用任何未验证的尺寸方法)
call(root, Obf.TABLE_ROW, new Class<?>[0], cellClass);
cell = call(root, Obf.TABLE_ADD, new Class<?>[] {actorClass}, cellClass, state.status);
layoutCell(cell, Obf.CELL_LEFT);
// 中段+下段放进**同一个竖直容器**:列表在上面 expand+fill(一直拉到日志上沿),
// 日志紧跟其下一行 —— 结构上保证"列表下边和日志区紧贴",不依赖任何高度猜测。
Object content = tableClass.getConstructor().newInstance();
call(content, Obf.TABLE_TOP, new Class<?>[0], tableClass);
Object contentCell = call(content, Obf.TABLE_ADD, new Class<?>[] {actorClass}, cellClass, scroll);
layoutCell(contentCell, Obf.CELL_TOP, Obf.CELL_EXPAND, Obf.CELL_FILL);
state.listCell = contentCell;
state.content = content;
state.root = root;
call(content, Obf.TABLE_ROW, new Class<?>[0], cellClass);
Object logCell = call(content, Obf.TABLE_ADD, new Class<?>[] {actorClass}, cellClass,
state.status);
layoutCell(logCell, Obf.CELL_LEFT);
cell = call(root, Obf.TABLE_ADD, new Class<?>[] {actorClass}, cellClass, content);
layoutCell(cell, Obf.CELL_EXPAND, Obf.CELL_FILL);
wire(loader, setValue, new Runnable() {
@Override
@@ -182,6 +191,67 @@ public final class ObjectPanel {
});
startSelectionWatcher(state);
// 一次性诊断:等布局稳定后把 ScrollPane / 日志 Label 的几何打出来(只读,不改行为)
Thread measure = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2500);
} catch (InterruptedException ignored) {
return;
}
StringBuilder a = new StringBuilder("[panel] 列表 ScrollPane 几何: ");
for (Method m : state.listScroll.getClass().getMethods()) {
if (m.getParameterTypes().length != 0 || m.getReturnType() != float.class) continue;
try {
m.setAccessible(true);
a.append(m.getName()).append('=').append(m.invoke(state.listScroll)).append(' ');
} catch (Throwable ignored) {
// 跳过
}
}
StringBuilder b = new StringBuilder("[panel] 日志 Label 几何: ");
for (Method m : state.status.getClass().getMethods()) {
if (m.getParameterTypes().length != 0 || m.getReturnType() != float.class) continue;
try {
m.setAccessible(true);
b.append(m.getName()).append('=').append(m.invoke(state.status)).append(' ');
} catch (Throwable ignored) {
// 跳过
}
}
// 同一方法名在 root/content/pane 上各读一遍:真正的 getWidth/getHeight 会给出"面板那么大"的数
for (Method m : state.listScroll.getClass().getMethods()) {
if (m.getParameterTypes().length != 0 || m.getReturnType() != float.class) continue;
String name = m.getName();
StringBuilder row = new StringBuilder("[panel] " + name + ": ");
Object[] targets = {state.root, state.content, state.listScroll, state.status};
String[] tags = {"root", "content", "pane", "log"};
for (int i = 0; i < targets.length; i++) {
if (targets[i] == null) continue;
try {
Method same = null;
for (Method candidate : targets[i].getClass().getMethods()) {
if (candidate.getName().equals(name)
&& candidate.getParameterTypes().length == 0
&& candidate.getReturnType() == float.class) {
same = candidate;
break;
}
}
if (same == null) continue;
same.setAccessible(true);
row.append(tags[i]).append('=').append(same.invoke(targets[i])).append(' ');
} catch (Throwable ignored) {
// 跳过
}
}
System.err.println(row.toString());
}
}
}, "spineview-measure");
measure.setDaemon(true);
measure.start();
System.err.println("[panel] 增删改查面板已建好");
refreshList(state);
return root;
@@ -406,7 +476,9 @@ public final class ObjectPanel {
value = "<" + error.getClass().getSimpleName() + ">";
}
final int rowIndex = lines.size();
lines.add(" " + shortType(field.getType().getName()) + " " + field.getName()
String shown = realName(type.getName(), field.getName(), field.getType());
lines.add(" " + shortType(field.getType().getName()) + " " + shown
+ (shown.equals(field.getName()) ? "" : "(" + field.getName() + ")")
+ " = " + value + " @" + type.getName());
fieldRows.put(Integer.valueOf(rowIndex), new Object[] {object, field, value});
fields.add(new Object[] {object, field, value});
@@ -421,7 +493,15 @@ public final class ObjectPanel {
if (params.length() > 0) params.append(", ");
params.append(shortType(parameter.getName()));
}
lines.add(" " + shortType(method.getReturnType().getName()) + " " + method.getName()
StringBuilder descriptor = new StringBuilder("(");
for (Class<?> parameter : method.getParameterTypes()) {
descriptor.append(descriptorOf(parameter));
}
descriptor.append(')').append(descriptorOf(method.getReturnType()));
String methodShown = realName(method.getDeclaringClass().getName(), method.getName(),
descriptor.toString());
lines.add(" " + shortType(method.getReturnType().getName()) + " " + methodShown
+ (methodShown.equals(method.getName()) ? "" : "(" + method.getName() + ")")
+ "(" + params + ")");
}
fillRows(state, lines, fieldRows);
@@ -928,8 +1008,70 @@ public final class ObjectPanel {
private static volatile boolean routerStarted;
/** 验证用:记住面板上的按钮(标签 → 按钮),探针可以按标签"点"它们。 */
/**
* 混淆名 → 真名 的映射表(读 tools/deobf/names.tsv)。key = owner#混淆名#描述符。
*
* 为什么需要它:编辑器**运行时**的类和成员名不能改(它的 indy 靠加密串里的原名反射查找),
* 所以反射拿到的是 `C/L/I/S/D…`;面板显示时用这张表翻译成有意义的名字。
*/
private static final java.util.Map<String, String> NAMES =
new java.util.HashMap<String, String>();
/** 上一次 build 的 state(按钮路由里用来在动作后抢滚动焦点)。 */
private static volatile ObjectPanel.State LAST_STATE;
private static void loadNames() {
if (!NAMES.isEmpty()) return;
try {
String classes = System.getProperty("spine.classes");
if (classes == null) return;
java.io.File root = new java.io.File(classes).getParentFile().getParentFile();
java.io.File table = new java.io.File(root, "tools/deobf/names.tsv");
if (!table.isFile()) {
System.err.println("[panel] 没找到映射表: " + table);
return;
}
for (String line : java.nio.file.Files.readAllLines(table.toPath(),
java.nio.charset.StandardCharsets.UTF_8)) {
if (line.isEmpty() || line.startsWith("#")) continue;
String[] parts = line.split("\t");
if (parts.length < 4) continue;
NAMES.put(parts[0] + "#" + parts[1] + "#" + parts[2], parts[3]);
}
System.err.println("[panel] 已载入映射表 " + NAMES.size() + " 条:" + table);
} catch (Throwable error) {
System.err.println("[panel] 载入映射表失败: " + error);
}
}
/** 类型的 JVM 描述符(用于查映射表)。 */
private static String descriptorOf(Class<?> type) {
if (type == void.class) return "V";
if (type == boolean.class) return "Z";
if (type == byte.class) return "B";
if (type == char.class) return "C";
if (type == short.class) return "S";
if (type == int.class) return "I";
if (type == long.class) return "J";
if (type == float.class) return "F";
if (type == double.class) return "D";
if (type.isArray()) return "[" + descriptorOf(type.getComponentType());
return "L" + type.getName().replace('.', '/') + ";";
}
/** 把"声明类 + 混淆名 + 类型"翻成真名;查不到就返回原名。 */
static String realName(String owner, String obfName, Class<?> type) {
loadNames();
String hit = NAMES.get(owner + "#" + obfName + "#" + descriptorOf(type));
return hit == null ? obfName : hit;
}
static String realName(String owner, String obfName, String descriptor) {
loadNames();
String hit = NAMES.get(owner + "#" + obfName + "#" + descriptor);
return hit == null ? obfName : hit;
}
private static final java.util.Map<String, Object> BUTTONS =
new java.util.LinkedHashMap<String, Object>();