Files
Spine_lab/tools/live/src/Probe5.java
T
tianmo 7f519edc67 热更(不重启)整理新建附件:用编辑器自身 API 重置图片附件变换 + 删除占位(skin)附件
- 确认 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。
2026-10-16 18:26:40 +08:00

233 lines
8.1 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import java.lang.instrument.Instrumentation;
import java.lang.reflect.*;
import java.net.URLClassLoader;
import java.util.*;
/**
* 只读探针 3:把骨架里所有 GO(图片附件)连同它的插槽、变换值列出;
* 再把皮肤 "animation" 的每个条目列出。目的是看清"多出来的那批附件"是什么。
*/
public final class Probe5 {
static Instrumentation INST;
static ClassLoader L;
static Class<?> ARRAY, NODE, GN;
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("[probe5] " + 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);
Class<?> GP = Class.forName("GP", true, L);
Class<?> GU = Class.forName("GU", true, L);
Class<?> GO = Class.forName("GO", true, L);
Class<?> GPC = Class.forName("Gp", true, L);
Class<?> GL = Class.forName("Gl", true, L);
Object roots = rootNodes(global("Uq"));
Object skeleton = firstOfType(roots, GP);
if (skeleton == null) { log("拿不到骨架"); return; }
log("骨架 = " + nameOf(skeleton));
// 插槽
Object slots = arrFieldOfType(GP, skeleton, GU);
for (int i = 0; i < size(slots); i++) {
Object s = get(slots, i);
log(" slot[" + i + "] name=" + nameOf(s) + " bone=" + nameOf(field(s, GU, GPC)));
}
// 所有 GO 附件
for (Field f : GP.getDeclaredFields()) {
if (Modifier.isStatic(f.getModifiers()) || f.getType() != ARRAY) continue;
f.setAccessible(true);
Object v = f.get(skeleton);
if (size(v) == 0) continue;
Class<?> et = elemClass(v);
if (et == null || !GO.isAssignableFrom(et)) continue;
log("GP." + f.getName() + ":" + size(v) + " 个 GO");
for (int i = 0; i < size(v); i++) {
Object go = get(v, i);
log(" GO[" + i + "] name=" + nameOf(go)
+ " slot=" + nameOf(field(go, GN, GU))
+ " x=" + fval(go, "x") + " y=" + fval(go, "y") + " rot=" + fval(go, "rot")
+ " sx=" + fval(go, "sx") + " sy=" + fval(go, "sy")
+ " w=" + fval(go, "w") + " h=" + fval(go, "h"));
}
}
// 皮肤条目
Object skins = arrFieldOfType(GP, skeleton, GL);
for (int k = 0; k < size(skins); k++) {
Object skin = get(skins, k);
log("皮肤[" + k + "] " + nameOf(skin));
for (int i = 0; i < size(slots); i++) {
Object s = get(slots, i);
Object entry = null;
for (Method m : GL.getDeclaredMethods()) {
Class<?>[] p = m.getParameterTypes();
if (p.length != 2 || p[0] != GPC || p[1] != GU) continue;
m.setAccessible(true);
try { entry = m.invoke(skin, field(s, GU, GPC), s); } catch (Throwable ignored) { }
if (entry != null) break;
}
if (entry != null) {
log(" 条目 slot=" + nameOf(s) + " -> " + describe(entry) + fields(entry));
}
}
}
}
/** GO 的字段语义来自编辑器自己的 JSON 写入器 sb_0.java:669-675。 */
static String fval(Object go, String which) {
String[] names = { "D", "B", "S", "L", "I", "C", "Z" };
String[] keys = { "x", "y", "rot", "sx", "sy", "w", "h" };
for (int i = 0; i < keys.length; i++) {
if (!keys[i].equals(which)) continue;
try {
Field f = go.getClass().getField(names[i]);
return String.valueOf(f.getFloat(go));
} catch (Throwable t) { return "?"; }
}
return "?";
}
static String fields(Object o) {
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() > 60) s = s.substring(0, 60) + "…";
sb.append(" ").append(f.getName()).append('=').append(s);
}
}
return sb.toString();
}
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 String describe(Object o) { return o == null ? "null" : o.getClass().getName(); }
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;
}
}