178 lines
6.7 KiB
Java
178 lines
6.7 KiB
Java
import java.lang.instrument.Instrumentation;
|
||
import java.lang.reflect.*;
|
||
import java.net.URLClassLoader;
|
||
|
||
/**
|
||
* 只读探针 6:把面板的**整棵控件树**连坐标打出来(含 stage 坐标),
|
||
* 用来定位"列表里的内容被画到面板外"。
|
||
*
|
||
* 取值方法来自字节码(javap -p -c):
|
||
* dl.c_()F=getX dl.d_()F=getY dl.L()F=getWidth dl.I()F=getHeight
|
||
* dl.D()Ldo; = getParent do.D()Lhq; = getChildren hq extends fI(Array),size 字段在 fI 里
|
||
* ⚠️ et(Table) 自己也声明了 `int L()` / `int I()`(列数/行数),所以必须**按返回类型 float 过滤**。
|
||
*/
|
||
public final class GeomProbe6 {
|
||
static Instrumentation INST;
|
||
static Object STATE;
|
||
static Class<?> HQL, ARR, AO;
|
||
|
||
public static void agentmain(String args, Instrumentation inst) {
|
||
INST = inst;
|
||
try { run(); } catch (Throwable e) { log("失败: " + e); e.printStackTrace(System.err); }
|
||
}
|
||
|
||
static void log(String m) { System.err.println("[geom6] " + m); }
|
||
|
||
static void run() throws Exception {
|
||
for (Class<?> c : INST.getAllLoadedClasses()) {
|
||
if (!c.getName().equals("com.spineview.ObjectPanel")) continue;
|
||
try {
|
||
Field f = c.getDeclaredField("LAST_STATE");
|
||
f.setAccessible(true);
|
||
Object v = f.get(null);
|
||
if (v != null) STATE = v;
|
||
} catch (Throwable ignored) { }
|
||
}
|
||
if (STATE == null) { log("LAST_STATE 拿不到"); return; }
|
||
ClassLoader payload = null;
|
||
for (Class<?> c : INST.getAllLoadedClasses()) {
|
||
if (c.getClassLoader() instanceof URLClassLoader) { payload = c.getClassLoader(); break; }
|
||
}
|
||
HQL = Class.forName("hq", true, payload);
|
||
ARR = Class.forName("fI", true, payload);
|
||
AO = Class.forName("dl", true, payload);
|
||
Class<?> og = null;
|
||
for (Class<?> c : INST.getAllLoadedClasses()) if (c.getName().equals("OG")) og = c;
|
||
Method onRender = null;
|
||
for (Method m : og.getDeclaredMethods()) {
|
||
Class<?>[] t = m.getParameterTypes();
|
||
if (m.getName().equals("B") && t.length == 1 && t[0] == Runnable.class) { onRender = m; break; }
|
||
}
|
||
onRender.setAccessible(true);
|
||
onRender.invoke(null, new Runnable() {
|
||
@Override public void run() {
|
||
try { work(STATE); } catch (Throwable e) { log("work 失败: " + e); e.printStackTrace(System.err); }
|
||
}
|
||
});
|
||
}
|
||
|
||
static void work(Object state) throws Exception {
|
||
Object root = state("root");
|
||
log("=========== 面板控件树(缩进=层级;坐标为 stage 绝对坐标)===========");
|
||
walk(root, 0, 0f, 0f, 6);
|
||
log("=========== 面板根再往上(谁给它多大地方)===========");
|
||
Object p = parent(root);
|
||
float ax = 0f, ay = 0f;
|
||
for (int i = 0; i < 6 && p != null; i++) {
|
||
log(" 上级[" + i + "] " + p.getClass().getName() + " " + geom(p));
|
||
Object np = parent(p);
|
||
if (np == p) break;
|
||
p = np;
|
||
}
|
||
log("=========== 关键对象定位 ===========");
|
||
Object scroll = state("listScroll"), table = state("listTable");
|
||
log(" listScroll " + geom(scroll) + " 子控件=" + size(childrenOf(scroll)));
|
||
log(" listTable " + geom(table) + " 行数=" + size(childrenOf(table)));
|
||
log(" listTable 的 stage 坐标 = " + stageX(table) + "," + stageY(table));
|
||
log("=======================================================");
|
||
}
|
||
|
||
static Object state(String name) throws Exception {
|
||
Field f = STATE.getClass().getDeclaredField(name);
|
||
f.setAccessible(true);
|
||
return f.get(STATE);
|
||
}
|
||
|
||
static void walk(Object actor, int depth, float px, float py, int maxDepth) {
|
||
if (actor == null || depth > maxDepth) return;
|
||
StringBuilder ind = new StringBuilder();
|
||
for (int i = 0; i < depth; i++) ind.append(" ");
|
||
float x = fl(actor, "c_"), y = fl(actor, "d_");
|
||
Object kids = childrenOf(actor);
|
||
int n = size(kids);
|
||
log(ind + actor.getClass().getName() + " " + geom(actor)
|
||
+ " stage=(" + r(px + x) + "," + r(py + y) + ") 子控件=" + n);
|
||
for (int i = 0; i < n; i++) {
|
||
Object child = get(kids, i);
|
||
walk(child, depth + 1, px + x, py + y, maxDepth);
|
||
}
|
||
}
|
||
|
||
static float stageX(Object actor) {
|
||
float x = 0f;
|
||
Object a = actor;
|
||
for (int i = 0; i < 8 && a != null; i++) { x += fl(a, "c_"); a = parent(a); }
|
||
return r(x);
|
||
}
|
||
|
||
static float stageY(Object actor) {
|
||
float y = 0f;
|
||
Object a = actor;
|
||
for (int i = 0; i < 8 && a != null; i++) { y += fl(a, "d_"); a = parent(a); }
|
||
return r(y);
|
||
}
|
||
|
||
static String geom(Object actor) {
|
||
if (actor == null) return "null";
|
||
return "[local x=" + r(fl(actor, "c_")) + " y=" + r(fl(actor, "d_"))
|
||
+ " w=" + r(fl(actor, "L")) + " h=" + r(fl(actor, "I"))
|
||
+ " top=" + r(fl(actor, "S")) + " right=" + r(fl(actor, "C")) + "]";
|
||
}
|
||
|
||
static Object parent(Object actor) {
|
||
if (actor == null) return null;
|
||
for (Method m : actor.getClass().getMethods()) {
|
||
if (m.getParameterTypes().length != 0) continue;
|
||
if (!m.getName().equals("D")) continue;
|
||
if (!m.getReturnType().getName().equals("do")) continue;
|
||
try { m.setAccessible(true); return m.invoke(actor); } catch (Throwable ignored) { }
|
||
}
|
||
return null;
|
||
}
|
||
|
||
static Object childrenOf(Object actor) {
|
||
if (actor == null) return null;
|
||
for (Method m : actor.getClass().getMethods()) {
|
||
if (m.getParameterTypes().length != 0) continue;
|
||
if (m.getReturnType() != HQL) continue;
|
||
try { m.setAccessible(true); return m.invoke(actor); } catch (Throwable ignored) { }
|
||
}
|
||
return null;
|
||
}
|
||
|
||
static int size(Object array) {
|
||
if (array == null) return 0;
|
||
for (Class<?> t = ARR; t != null && t != Object.class; t = t.getSuperclass()) {
|
||
for (Field f : t.getDeclaredFields()) {
|
||
if (f.getType() != int.class) continue;
|
||
f.setAccessible(true);
|
||
try { return f.getInt(array); } catch (Throwable ignored) { }
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static Object get(Object array, int index) {
|
||
if (array == null) return null;
|
||
for (Method m : array.getClass().getMethods()) {
|
||
if (!m.getName().equals("D")) continue;
|
||
Class<?>[] t = m.getParameterTypes();
|
||
if (t.length != 1 || t[0] != int.class || m.getReturnType() != Object.class) continue;
|
||
try { m.setAccessible(true); return m.invoke(array, Integer.valueOf(index)); } catch (Throwable ignored) { }
|
||
}
|
||
return null;
|
||
}
|
||
|
||
static float fl(Object actor, String name) {
|
||
if (actor == null) return -1f;
|
||
for (Method m : actor.getClass().getMethods()) {
|
||
if (!m.getName().equals(name)) continue;
|
||
if (m.getParameterTypes().length != 0 || m.getReturnType() != float.class) continue;
|
||
try { m.setAccessible(true); return ((Float) m.invoke(actor)).floatValue(); } catch (Throwable ignored) { }
|
||
}
|
||
return -1f;
|
||
}
|
||
|
||
static float r(float v) { return Math.round(v * 100f) / 100f; }
|
||
}
|