Files
Spine_lab/tools/live/src/Probe9.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

222 lines
8.0 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.*;
/**
* 只读探针 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;
}
}