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; } }