- 确认 GO 的字段/入口:x=D(float)、y=B(float)、rotation=S、scaleX=L、scaleY=I、width=C、height=Z; GO.D(FFFFF)V 就是编辑器自己的 setTransform(x,y,rotation,scaleX,scaleY)。 依据:载荷 JSON 写入器 sb_0.java:669-675 + javap -c GO(两条独立证据), width/height 与 mega_dg4.png 真实像素 157x154 对上。 - 确认删除入口:Gn.L()V => skeleton.removeAttachment(this,true,true);GP.L=附件表、GP.Z=插槽表; 占位附件是载荷类 Hi(hi_2.java 的 I() 返回 "placeholder")。 - 实机(JDK8 attach,进程 56177 全程没重启):20 个新建插槽的图片附件 x/y/rotation 归 0、scale 归 1; 20 个 Hi 占位附件删除;骨架附件表 43 -> 23;逐个核对删除占位没连带删掉图片附件。 - 顺带把 tools/live(attach 热更工具源码)入库,jar/build 加进 .gitignore。
222 lines
8.0 KiB
Java
222 lines
8.0 KiB
Java
import java.lang.instrument.Instrumentation;
|
||
import java.lang.reflect.*;
|
||
import java.net.URLClassLoader;
|
||
import java.util.*;
|
||
|
||
/**
|
||
* 只读探针 4:一个插槽到底挂了几份附件、各是什么类;以及骨架附件表里每一项的类名和变换。
|
||
*/
|
||
public final class Probe9 {
|
||
static Instrumentation INST;
|
||
static ClassLoader L;
|
||
static Class<?> ARRAY, NODE, GN, GO;
|
||
|
||
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("[probe9] " + m); }
|
||
|
||
static void run() throws Exception {
|
||
for (Class<?> c : INST.getAllLoadedClasses()) {
|
||
if (c.getClassLoader() instanceof URLClassLoader) { L = c.getClassLoader(); break; }
|
||
}
|
||
if (L == null) { log("找不到编辑器类加载器"); return; }
|
||
ARRAY = Class.forName("fI", true, L);
|
||
NODE = Class.forName("eM", true, L);
|
||
GN = Class.forName("Gn", true, L);
|
||
GO = Class.forName("GO", true, L);
|
||
Class<?> GP = Class.forName("GP", true, L);
|
||
Class<?> GU = Class.forName("GU", true, L);
|
||
Class<?> GPC = Class.forName("Gp", true, L);
|
||
Method cur = method(GU, "D", new Class<?>[] {}, GN);
|
||
Method setup = method(GU, "B", new Class<?>[] {}, GN);
|
||
|
||
Object skeleton = firstOfType(rootNodes(global("Uq")), GP);
|
||
Object slots = arrFieldOfType(GP, skeleton, GU);
|
||
Object probe = null;
|
||
for (int i = 0; i < size(slots); i++) {
|
||
if ("mega_dg7".equals(nameOf(get(slots, i)))) { probe = get(slots, i); break; }
|
||
}
|
||
log("样本插槽 = " + nameOf(probe));
|
||
Object a = cur.invoke(probe), b = setup.invoke(probe);
|
||
log(" 当前附件 = " + describe(a));
|
||
log(" 静止附件 = " + describe(b));
|
||
log(" 是同一个对象吗 = " + (a == b));
|
||
log(" 当前附件字段:" + fields(a));
|
||
log(" 静止附件字段:" + fields(b));
|
||
Object bone = field(probe, GU, GPC);
|
||
log(" 所在骨骼 = " + nameOf(bone));
|
||
|
||
// 骨架里所有 Gn 附件
|
||
for (Field f : GP.getDeclaredFields()) {
|
||
if (Modifier.isStatic(f.getModifiers()) || f.getType() != ARRAY) continue;
|
||
f.setAccessible(true);
|
||
Object v = f.get(skeleton);
|
||
Class<?> et = elemClass(v);
|
||
if (et == null || !GN.isAssignableFrom(et)) continue;
|
||
log("GP." + f.getName() + ":" + size(v) + " 个附件");
|
||
for (int i = 0; i < size(v); i++) {
|
||
Object o = get(v, i);
|
||
log(" [" + i + "] " + o.getClass().getName() + " name=" + nameOf(o)
|
||
+ " slot=" + nameOf(field(o, GN, GU)) + " 当前?" + (o == a) + " 静止?" + (o == b)
|
||
+ (GO.isInstance(o) ? " " + goValues(o) : " " + fields(o)));
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 字段语义来自编辑器自己的 JSON 写入器 sb_0.java:669-675。
|
||
* x 特意按"声明里第一个 float 类型的 D 字段"取:GO 上有多个同名字段 D,按名字 getDeclaredField 会取错。
|
||
*/
|
||
static String goValues(Object go) {
|
||
String[] names = { "D", "B", "S", "L", "I", "C", "Z" };
|
||
String[] keys = { "x", "y", "rotation", "scaleX", "scaleY", "width", "height" };
|
||
StringBuilder sb = new StringBuilder();
|
||
for (int i = 0; i < keys.length; i++) {
|
||
try {
|
||
Field f = null;
|
||
for (Field cand : GO.getDeclaredFields()) {
|
||
if (cand.getName().equals(names[i]) && cand.getType() == float.class) { f = cand; break; }
|
||
}
|
||
f.setAccessible(true);
|
||
sb.append(keys[i]).append('=').append(f.getFloat(go)).append(' ');
|
||
} catch (Throwable t) { sb.append(keys[i]).append("=? "); }
|
||
}
|
||
return sb.toString();
|
||
}
|
||
|
||
static String fields(Object o) {
|
||
if (o == null) return "null";
|
||
StringBuilder sb = new StringBuilder();
|
||
for (Class<?> t = o.getClass(); t != null && t != Object.class; t = t.getSuperclass()) {
|
||
for (Field f : t.getDeclaredFields()) {
|
||
if (Modifier.isStatic(f.getModifiers())) continue;
|
||
f.setAccessible(true);
|
||
Object v;
|
||
try { v = f.get(o); } catch (Throwable e) { continue; }
|
||
if (v == null) continue;
|
||
String s = String.valueOf(v);
|
||
if (s.length() > 50) s = s.substring(0, 50) + "…";
|
||
sb.append(" ").append(t.getSimpleName()).append('.').append(f.getName())
|
||
.append('=').append(s);
|
||
}
|
||
}
|
||
return sb.toString();
|
||
}
|
||
|
||
static String describe(Object o) { return o == null ? "null" : o.getClass().getName() + "@" + System.identityHashCode(o); }
|
||
|
||
static Object arrFieldOfType(Class<?> owner, Object target, Class<?> elem) {
|
||
for (Field f : owner.getDeclaredFields()) {
|
||
if (Modifier.isStatic(f.getModifiers()) || f.getType() != ARRAY) continue;
|
||
f.setAccessible(true);
|
||
try {
|
||
Object v = f.get(target);
|
||
Class<?> et = elemClass(v);
|
||
if (et != null && elem.isAssignableFrom(et)) return v;
|
||
} catch (Throwable ignored) { }
|
||
}
|
||
return null;
|
||
}
|
||
|
||
static Object rootNodes(Object tree) {
|
||
for (Class<?> t = tree.getClass(); t != null && t != Object.class; t = t.getSuperclass()) {
|
||
for (Field f : t.getDeclaredFields()) {
|
||
if (f.getType() != ARRAY) continue;
|
||
f.setAccessible(true);
|
||
try {
|
||
Object v = f.get(tree);
|
||
if (size(v) > 0 && NODE.isAssignableFrom(elemClass(v))) return v;
|
||
} catch (Throwable ignored) { }
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
static Object firstOfType(Object nodes, Class<?> type) {
|
||
if (nodes == null) return null;
|
||
for (int i = 0; i < size(nodes); i++) {
|
||
Object node = get(nodes, i);
|
||
Object obj = field(node, NODE, Object.class);
|
||
if (obj != null && type.isInstance(obj)) return obj;
|
||
Object deep = firstOfType(field(node, NODE, ARRAY), type);
|
||
if (deep != null) return deep;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
static Object field(Object target, Class<?> from, Class<?> type) {
|
||
if (target == null) return null;
|
||
for (Class<?> t = from; t != null && t != Object.class; t = t.getSuperclass()) {
|
||
for (Field f : t.getDeclaredFields()) {
|
||
if (Modifier.isStatic(f.getModifiers())) continue;
|
||
if (f.getType() != type) continue;
|
||
f.setAccessible(true);
|
||
try { return f.get(target); } catch (Throwable ignored) { }
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
static Object global(String typeName) {
|
||
try {
|
||
Class<?> og = Class.forName("OG", true, L);
|
||
for (Field f : og.getDeclaredFields()) {
|
||
if (!Modifier.isStatic(f.getModifiers())) continue;
|
||
if (!f.getType().getName().equals(typeName)) continue;
|
||
f.setAccessible(true);
|
||
Object v = f.get(null);
|
||
if (v != null) return v;
|
||
}
|
||
} catch (Throwable ignored) { }
|
||
return null;
|
||
}
|
||
|
||
static String nameOf(Object o) {
|
||
if (o == null) return "null";
|
||
try {
|
||
return (String) Class.forName("com.spineview.SceneObjects", true, L)
|
||
.getMethod("nameOf", Object.class).invoke(null, o);
|
||
} catch (Throwable t) { return "?"; }
|
||
}
|
||
|
||
static int size(Object a) {
|
||
if (a == null) return 0;
|
||
try {
|
||
for (Field f : ARRAY.getDeclaredFields()) {
|
||
if (f.getType() == int.class) { f.setAccessible(true); return f.getInt(a); }
|
||
}
|
||
} catch (Throwable ignored) { }
|
||
return 0;
|
||
}
|
||
|
||
static Object get(Object a, int i) {
|
||
if (a == null) return null;
|
||
try {
|
||
for (Field f : ARRAY.getDeclaredFields()) {
|
||
if (f.getType() == Object[].class) { f.setAccessible(true); return ((Object[]) f.get(a))[i]; }
|
||
}
|
||
} catch (Throwable ignored) { }
|
||
return null;
|
||
}
|
||
|
||
static Class<?> elemClass(Object array) {
|
||
for (int i = 0; i < size(array); i++) { Object o = get(array, i); if (o != null) return o.getClass(); }
|
||
return null;
|
||
}
|
||
|
||
static Method method(Class<?> owner, String name, Class<?>[] params, Class<?> ret) {
|
||
for (Class<?> t = owner; t != null && t != Object.class; t = t.getSuperclass()) {
|
||
for (Method m : t.getDeclaredMethods()) {
|
||
if (!m.getName().equals(name) || !Arrays.equals(m.getParameterTypes(), params)) continue;
|
||
if (ret != null && m.getReturnType() != ret) continue;
|
||
m.setAccessible(true);
|
||
return m;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
}
|