Files
Spine_lab/viewpanel/src/com/spineview/NewFifteen.java
T
tianmo 8df990a058 把 New 15 Bone+Slot+Att 按钮移植进面板源码(重启后仍在)
- 新类 com.spineview.NewFifteen:按钮 → 编辑器自带名称面板(xl + ASM 子类拿名字) → 按名字匹配附件
  → 当前选中骨骼下建 15 根骨骼,每根 1 插槽 1 附件(该名字的副本)
  骨骼 x/y 0~500 随机、rotation/scale/shear=0/1/0;插槽 alpha 0.5~1、blend normal/additive 随机;
  附件 x/y/rotation=0、scaleX/scaleY=1(并替换掉 ADD_SLOT 带过来的模板附件)
  弹窗按 pref 尺寸(>=360x170)撑开、同一次渲染回调里居中
- ObjectPanel.build 里挂一行按钮(复用 ObjectPanel.wire 的点击轮询)
- build.sh 的 javac 加 -cp ../tools/asm/asm.jar(NewFifteen 现场生成 xl 子类需要 ASM)
依据:tools/deobf/anchors.tsv + 混淆名对照表.md(全部字节码/实测确认)
2026-10-17 10:25:22 +08:00

683 lines
29 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.
package com.spineview;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Random;
import java.util.Set;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
/**
* “New 15 Bone+Slot+Att” 按钮(正式版,重启后仍在)。
*
* 流程:点一下 → 弹编辑器自带的名称输入面板 → 用输入的名字匹配骨架里的附件
* → 名字不对/找不到就结束 → 否则在**当前选中骨骼**下建 15 根骨骼,
* 每根 1 个插槽 + 1 个附件(该名字的附件副本)。
*
* 参数要求(全部按已确认的字段语义写,见 混淆名对照表.md):
* 骨骼:x/y 各 0~500 随机;rotation=0;scaleX=scaleY=1;shearX=shearY=0
* 插槽:颜色 alpha 0.5~1 随机;blend = normal / additive 随机
* 附件(挂在插槽上):x=y=rotation=0;scaleX=scaleY=1
*
* 关键 API(都在 tools/deobf/anchors.tsv 里,附字节码依据):
* UH.D(GU,Gp,Z,fI)GU addSlot ⚠️ 会连模板插槽的附件一起复制
* UH.D(Gn,GU,Z,Z,fI)Gn addAttachmentCopy(内部 Gn.D(GP) 复制模板附件)
* GP.D(Gp)V addBone
* OG.D(String,fI,GU)String uniqueName
* VZ.D(GP,Z,Z)V refresh
* un.D()/un.B() undoBegin/undoCommit
* Gn.L()V delete
* dt.D(dl)V Stage.addActor
* xl.D(dt) / xl.L(Object) / xl.L(String) 编辑器名称面板:显示 / OK-Cancel 回调 / 拿到名字(抽象)
*/
final class NewFifteen {
private static final int COUNT = 15;
private static final String BUTTON_TEXT = "New 15 Bone+Slot+Att";
/** 生成的对话框子类(ASM 现生成:xl 是抽象类,且必须在载荷类加载器那边实现)。 */
private static Class<?> dialogClass;
private static ClassLoader dialogLoader;
private static boolean installed;
private NewFifteen() {
}
/** 由 ObjectPanel.build 调用:往按钮表格里加一行按钮并接上点击。 */
static void install(final ClassLoader loader, Object skin, Object buttonsTable,
Class<?> tableClass, Class<?> cellClass, Class<?> actorClass) {
if (installed) return;
try {
Class<?> textButton = Class.forName("com.badlogic.gdx.scenes.scene2d.ui.TextButton", true, loader);
Class<?> skinClass = Class.forName("com.badlogic.gdx.scenes.scene2d.ui.Skin", true, loader);
final Object button = textButton.getConstructor(String.class, skinClass)
.newInstance(BUTTON_TEXT, skin);
Object cell = call(buttonsTable, "D", new Class<?>[] {actorClass}, cellClass, button);
call(cell, "C", new Class<?>[0], cellClass); // Cell.left()
call(buttonsTable, "I", new Class<?>[0], cellClass); // Table.row()
ObjectPanel.wire(loader, button, new Runnable() {
@Override public void run() {
// 面板路由线程里:只负责开线程走流程,别把它堵住(要等用户输入)
Thread thread = new Thread(new Runnable() {
@Override public void run() {
try {
promptThenCreate(loader);
} catch (Throwable error) {
System.err.println("[new15] 流程失败: " + error);
error.printStackTrace(System.err);
}
}
}, "spineview-new15");
thread.setDaemon(true);
thread.start();
}
});
installed = true;
System.err.println("[new15] 按钮已加入: " + BUTTON_TEXT);
} catch (Throwable error) {
System.err.println("[new15] 加按钮失败: " + error);
}
}
// ==================== 弹面板 → 等名字 → 建对象 ====================
private static void promptThenCreate(final ClassLoader loader) throws Exception {
final Object[] holder = new Object[1];
SceneActions.onEditorThread(loader, new Runnable() {
@Override public void run() {
try {
holder[0] = showPrompt(loader);
} catch (Throwable error) {
System.err.println("[new15] 弹面板失败: " + error);
error.printStackTrace(System.err);
}
}
});
final Object dialog = holder[0];
if (dialog == null) {
System.err.println("[new15] 面板没建出来");
return;
}
String name = waitForName(loader, dialog, 300000L);
if (name == null || name.trim().isEmpty()) {
System.err.println("[new15] 没拿到名字(取消/超时)→ 结束");
removeQuietly(dialog);
return;
}
final String finalName = name.trim();
System.err.println("[new15] 输入的名字 = " + finalName);
removeQuietly(dialog);
SceneActions.onEditorThread(loader, new Runnable() {
@Override public void run() {
try {
create(loader, finalName);
} catch (Throwable error) {
System.err.println("[new15] 建对象失败: " + error);
error.printStackTrace(System.err);
}
}
});
}
/** 在渲染线程里:建对话框 → 挂进 Stage → 按 pref 撑开 → 居中(都在同一帧里完成,不会先闪别处)。 */
private static Object showPrompt(ClassLoader loader) throws Exception {
Class<?> xl = Class.forName("xl", true, loader);
Class<?> stageClass = Class.forName("dt", true, loader);
Object stage = global(loader, stageClass);
if (stage == null) throw new IllegalStateException("拿不到 Stage");
Class<?> dialog = dialogClass(loader);
Object dialogObject = dialog.getConstructor(String.class, String.class)
.newInstance("New Bones + Slots + Attachment",
"Enter an existing attachment name in this skeleton:");
// ① 挂进 Stage(实测 xl.D(dt) 不会挂;Stage.addActor 用"扫描 getMethods"那个才生效)
addActor(stage, dialogObject);
// ② 走编辑器自己的显示流程(把键盘焦点给名字框)
Method show = null;
for (Method method : xl.getMethods()) {
Class<?>[] types = method.getParameterTypes();
if (types.length == 1 && types[0] == stageClass) show = method;
}
if (show != null) {
try {
show.setAccessible(true);
show.invoke(dialogObject, stage);
} catch (Throwable error) {
System.err.println("[new15] xl.D(dt) 失败: " + error);
}
}
// ③ 尺寸:默认 150x150 会把正文/按钮裁掉 → 用 pref(下限 360x170)
float prefW = floatOf(dialogObject, "StackMapTable"); // getPrefWidth
float prefH = floatOf(dialogObject, "init"); // getPrefHeight
float minW = floatOf(dialogObject, "LineNumberTable"); // getMinWidth
float minH = floatOf(dialogObject, "String"); // getMinHeight
float width = Math.max(Math.max(prefW, minW), 360f);
float height = Math.max(Math.max(prefH, minH), 170f);
setFloatField(loader, dialogObject, "L", width); // Actor.width
setFloatField(loader, dialogObject, "I", height); // Actor.height
invalidate(loader, dialogObject);
// ④ 居中:setPosition(x,y,align) 在 align=0 时是"以 (x,y) 为中心"
center(stage, dialogObject);
return dialogObject;
}
/** 轮询用户操作:OK 时 NAME 有值;Cancel 时 DONE=true 且 NAME 为空;窗口被移除时 stage 字段变 null。 */
private static String waitForName(ClassLoader loader, Object dialog, long timeoutMs)
throws Exception {
Field nameField = dialog.getClass().getField("NAME");
Field doneField = dialog.getClass().getField("DONE");
Field stageField = fieldOfType(Class.forName("dl", true, loader),
Class.forName("dt", true, loader));
long deadline = System.currentTimeMillis() + timeoutMs;
while (System.currentTimeMillis() < deadline) {
Object name = nameField.get(dialog);
if (name != null) return (String) name;
if (Boolean.TRUE.equals(doneField.get(dialog))) return null;
if (stageField != null && stageField.get(dialog) == null) return null;
Thread.sleep(120L);
}
return null;
}
// ==================== 建 15 组对象 ====================
private static void create(ClassLoader loader, String name) throws Exception {
Class<?> boneClass = Class.forName("Gp", true, loader);
Class<?> slotClass = Class.forName("GU", true, loader);
Class<?> attClass = Class.forName("Gn", true, loader);
Class<?> hiClass = Class.forName("Hi", true, loader);
Class<?> skeletonClass = Class.forName("GP", true, loader);
Class<?> arrayClass = Class.forName("fI", true, loader);
Class<?> colorClass = Class.forName("com.badlogic.gdx.graphics.Color", true, loader);
Class<?> blendClass = Class.forName("iz", true, loader);
Class<?> uhClass = Class.forName("UH", true, loader);
Class<?> og = Class.forName("OG", true, loader);
Class<?> undoClass = Class.forName("un", true, loader);
Class<?> treeClass = Class.forName("VZ", true, loader);
Object selected = SceneObjects.selected(loader);
if (selected == null || !boneClass.isInstance(selected)) {
System.err.println("[new15] 当前选中的不是骨骼 → 结束");
return;
}
Object skeleton = firstFieldOfType(boneClass, selected, skeletonClass);
if (skeleton == null) {
System.err.println("[new15] 拿不到骨架 → 结束");
return;
}
Object bones = arrayOfType(skeletonClass, skeleton, arrayClass, boneClass);
Object slots = arrayOfType(skeletonClass, skeleton, arrayClass, slotClass);
Object attachments = arrayOfType(skeletonClass, skeleton, arrayClass, attClass);
if (bones == null || slots == null || attachments == null) {
System.err.println("[new15] 骨架的骨骼/插槽/附件表没找齐 → 结束");
return;
}
// 按名字找附件(优先非 Hi 占位)
Object template = null;
for (int i = 0; i < size(arrayClass, attachments); i++) {
Object candidate = get(arrayClass, attachments, i);
if (candidate == null || !attClass.isInstance(candidate)) continue;
if (!name.equals(SceneObjects.nameOf(candidate))) continue;
if (!hiClass.isInstance(candidate)) {
template = candidate;
break;
}
if (template == null) template = candidate;
}
if (template == null) {
System.err.println("[new15] 骨架里没有名为 \"" + name + "\" 的附件 → 结束");
return;
}
Object templateSlot = size(arrayClass, slots) > 0 ? get(arrayClass, slots, 0) : null;
Object uh = staticField(og, uhClass);
if (uh == null) {
System.err.println("[new15] 拿不到编辑器模型助手 UH → 结束");
return;
}
Method addBone = find(skeletonClass, "D", new Class<?>[] {boneClass}, void.class);
Method addSlot = find(uhClass, "D", new Class<?>[] {slotClass, boneClass, boolean.class,
arrayClass}, slotClass);
Method addAttachment = find(uhClass, "D", new Class<?>[] {attClass, slotClass, boolean.class,
boolean.class, arrayClass}, attClass);
Method nameGen = find(og, "D", new Class<?>[] {String.class, arrayClass, slotClass}, String.class);
Method refresh = find(treeClass, "D", new Class<?>[] {skeletonClass, boolean.class,
boolean.class}, void.class);
Method deleteAttachment = find(attClass, "L", new Class<?>[0], void.class);
Method undoBegin = noArgVoid(undoClass, "D");
Method undoCommit = noArgVoid(undoClass, "B");
if (addBone == null || addSlot == null || addAttachment == null || nameGen == null
|| undoBegin == null || undoCommit == null) {
System.err.println("[new15] 编辑器 API 没找齐,停止");
return;
}
Object blendNormal = blendClass.getField("D").get(null);
Object blendAdditive = blendClass.getField("B").get(null);
Constructor<?> boneCtor = boneClass.getConstructor(boneClass, skeletonClass, boneClass);
Object empty = arrayClass.getConstructor().newInstance();
Random random = new Random();
int made = 0;
undoBegin.setAccessible(true);
undoBegin.invoke(null);
for (int i = 0; i < COUNT; i++) {
// ---- 骨骼:x/y 0~500 随机,其余按 0/1 ----
Object bone = boneCtor.newInstance(selected, skeleton, selected);
set(bone, "D", String.class, nameGen.invoke(null, "bone", bones, null));
setFloatField(loader, bone, "Z", random.nextFloat() * 500f); // x
setFloatField(loader, bone, "V", random.nextFloat() * 500f); // y
setFloatField(loader, bone, "F", 0f); // rotation
setFloatField(loader, bone, "Code", 1f); // scaleX
setFloatField(loader, bone, "SourceFile", 1f); // scaleY
setFloatField(loader, bone, "StackMapTable", 0f); // shearX
setFloatField(loader, bone, "LineNumberTable", 0f); // shearY
addBone.setAccessible(true);
addBone.invoke(skeleton, bone);
// ---- 插槽:alpha 0.5~1 随机;blend normal/additive 随机 ----
Object out = arrayClass.getConstructor().newInstance();
addSlot.setAccessible(true);
Object slot = addSlot.invoke(uh, templateSlot, bone, Boolean.FALSE, out);
if (slot == null) continue;
// ADD_SLOT 会把模板插槽整个复制,连它挂着的附件一起复制 → 记下来待会删
Object inherited = readSlotAttachment(slotClass, attClass, slot);
float alpha = 0.5f + random.nextFloat() * 0.5f;
Object color = colorClass.getConstructor(float.class, float.class, float.class, float.class)
.newInstance(1f, 1f, 1f, alpha);
set(slot, "B", colorClass, color); // setup 颜色
set(slot, "D", colorClass, color); // animate 颜色
set(slot, "D", blendClass, random.nextBoolean() ? blendNormal : blendAdditive);
// ---- 附件:复制"输入名字"的附件,变换 0/0/0/1/1 ----
addAttachment.setAccessible(true);
Object attachment = addAttachment.invoke(uh, template, slot, Boolean.FALSE, Boolean.FALSE, empty);
if (attachment == null) continue;
setFloatField(loader, attachment, "D", 0f); // x
setFloatField(loader, attachment, "B", 0f); // y
setFloatField(loader, attachment, "S", 0f); // rotation
setFloatField(loader, attachment, "L", 1f); // scaleX
setFloatField(loader, attachment, "I", 1f); // scaleY
if (!name.equals(SceneObjects.nameOf(attachment))) {
set(attachment, "D", String.class, name); // 编辑器若自动改名,强制回输入的名字
}
// 插槽的当前附件 = 我们这份(两个字段都写,setup/animate 都覆盖)
set(slot, "B", attClass, attachment);
set(slot, "D", attClass, attachment);
// 删掉模板插槽带来的那份(保证"一根骨骼 = 1 插槽 + 1 附件")
if (inherited != null && inherited != attachment && deleteAttachment != null) {
deleteAttachment.setAccessible(true);
deleteAttachment.invoke(inherited);
}
made++;
}
undoCommit.setAccessible(true);
undoCommit.invoke(null);
if (refresh != null) {
refresh.setAccessible(true);
refresh.invoke(staticField(og, treeClass), skeleton, Boolean.TRUE, Boolean.TRUE);
}
System.err.println("[new15] 完成:在 " + SceneObjects.nameOf(selected) + " 下建了 " + made
+ " 根骨骼(每根 1 插槽 1 附件 \"" + name + "\")");
}
// ==================== 对话框子类(ASM) ====================
private static synchronized Class<?> dialogClass(final ClassLoader loader) throws Exception {
if (dialogClass != null) return dialogClass;
final String internal = "com/spineview/NewFifteenPrompt";
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
writer.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER, internal, null, "xl", null);
writer.visitField(Opcodes.ACC_PUBLIC, "NAME", "Ljava/lang/String;", null, null).visitEnd();
writer.visitField(Opcodes.ACC_PUBLIC, "DONE", "Z", null, null).visitEnd();
// 构造:super(title, body, (Ge) null)
MethodVisitor ctor = writer.visitMethod(Opcodes.ACC_PUBLIC, "<init>",
"(Ljava/lang/String;Ljava/lang/String;)V", null, null);
ctor.visitCode();
ctor.visitVarInsn(Opcodes.ALOAD, 0);
ctor.visitVarInsn(Opcodes.ALOAD, 1);
ctor.visitVarInsn(Opcodes.ALOAD, 2);
ctor.visitInsn(Opcodes.ACONST_NULL);
ctor.visitMethodInsn(Opcodes.INVOKESPECIAL, "xl", "<init>",
"(Ljava/lang/String;Ljava/lang/String;LGe;)V", false);
ctor.visitInsn(Opcodes.RETURN);
ctor.visitMaxs(0, 0);
ctor.visitEnd();
// 抽象回调:OK 时拿到名字 → 记 NAME,并关窗
MethodVisitor name = writer.visitMethod(Opcodes.ACC_PUBLIC, "L", "(Ljava/lang/String;)V",
null, null);
name.visitCode();
name.visitVarInsn(Opcodes.ALOAD, 0);
name.visitVarInsn(Opcodes.ALOAD, 1);
name.visitFieldInsn(Opcodes.PUTFIELD, internal, "NAME", "Ljava/lang/String;");
name.visitVarInsn(Opcodes.ALOAD, 0);
name.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "dl", "b_", "()Z", false); // remove()
name.visitInsn(Opcodes.POP);
name.visitInsn(Opcodes.RETURN);
name.visitMaxs(0, 0);
name.visitEnd();
// OK/Cancel 总回调:不管哪个都置 DONE
MethodVisitor done = writer.visitMethod(Opcodes.ACC_PUBLIC, "L", "(Ljava/lang/Object;)V",
null, null);
done.visitCode();
done.visitVarInsn(Opcodes.ALOAD, 0);
done.visitVarInsn(Opcodes.ALOAD, 1);
done.visitMethodInsn(Opcodes.INVOKESPECIAL, "xl", "L", "(Ljava/lang/Object;)V", false);
done.visitVarInsn(Opcodes.ALOAD, 0);
done.visitInsn(Opcodes.ICONST_1);
done.visitFieldInsn(Opcodes.PUTFIELD, internal, "DONE", "Z");
done.visitInsn(Opcodes.RETURN);
done.visitMaxs(0, 0);
done.visitEnd();
writer.visitEnd();
final byte[] bytes = writer.toByteArray();
dialogLoader = new ClassLoader(loader) {
@Override protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
if (name.equals("com.spineview.NewFifteenPrompt")) {
Class<?> found = findLoadedClass(name);
if (found == null) found = defineClass(name, bytes, 0, bytes.length);
if (resolve) resolveClass(found);
return found;
}
return super.loadClass(name, resolve);
}
};
dialogClass = Class.forName("com.spineview.NewFifteenPrompt", true, dialogLoader);
return dialogClass;
}
// ==================== 小工具 ====================
private static Object global(ClassLoader loader, Class<?> type) {
try {
Class<?> og = Class.forName("OG", true, loader);
for (Field field : og.getDeclaredFields()) {
if (!Modifier.isStatic(field.getModifiers()) || field.getType() != type) continue;
field.setAccessible(true);
Object value = field.get(null);
if (value != null) return value;
}
} catch (Throwable ignored) {
// 读不到就返回 null
}
return null;
}
/** 挂进 Stage:用"扫描 getMethods 里 1 个 Actor 参数 + void"的那个(getMethod 那个实测不生效)。 */
private static void addActor(Object stage, Object actor) throws Exception {
ClassLoader loader = stage.getClass().getClassLoader();
Class<?> actorClass = Class.forName("dl", true, loader);
Method add = null;
for (Method method : stage.getClass().getMethods()) {
Class<?>[] types = method.getParameterTypes();
if (types.length != 1) continue;
if (!actorClass.isAssignableFrom(types[0])) continue;
if (method.getReturnType() != void.class) continue;
add = method;
}
if (add == null) throw new NoSuchMethodException("Stage.addActor");
add.setAccessible(true);
add.invoke(stage, actor);
}
private static void center(Object stage, Object dialog) {
try {
float width = floatOf(dialog, "L"); // getWidth
float height = floatOf(dialog, "I"); // getHeight
float cx = floatOf(stage, "D") / 2f; // Stage.getWidth / 2
float cy = floatOf(stage, "B") / 2f; // Stage.getHeight / 2
Class<?> actorClass = Class.forName("dl", true, dialog.getClass().getClassLoader());
Method setPosition = null;
for (Method method : actorClass.getMethods()) {
Class<?>[] types = method.getParameterTypes();
if (types.length == 3 && types[0] == float.class && types[1] == float.class
&& types[2] == int.class) setPosition = method;
}
if (setPosition != null) {
setPosition.setAccessible(true);
// align=0 → 以 (x,y) 为中心
setPosition.invoke(dialog, Float.valueOf(cx), Float.valueOf(cy), Integer.valueOf(0));
}
System.err.println("[new15] 弹窗 " + width + "x" + height + " 居中于 (" + cx + "," + cy + ")");
} catch (Throwable error) {
System.err.println("[new15] 居中失败: " + error);
}
}
private static void invalidate(ClassLoader loader, Object dialog) {
try {
Class<?> widgetGroup = Class.forName("eX", true, loader);
for (Method method : widgetGroup.getMethods()) {
if (method.getName().equals("b_") && method.getParameterTypes().length == 0
&& method.getReturnType() == void.class) {
method.setAccessible(true);
method.invoke(dialog);
return;
}
}
} catch (Throwable ignored) {
// 没 invalidate 也不致命
}
}
private static void removeQuietly(Object dialog) {
try {
Class<?> actorClass = Class.forName("dl", true, dialog.getClass().getClassLoader());
Method remove = null;
for (Method method : actorClass.getMethods()) {
if (method.getName().equals("b_") && method.getParameterTypes().length == 0
&& method.getReturnType() == boolean.class) remove = method;
}
if (remove != null) {
remove.setAccessible(true);
remove.invoke(dialog);
}
} catch (Throwable ignored) {
// 已经关掉了
}
}
private static Object readSlotAttachment(Class<?> slotClass, Class<?> attClass, Object slot)
throws Exception {
for (Class<?> type = slotClass; type != null && type != Object.class; type = type.getSuperclass()) {
for (Field field : type.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers()) || field.getType() != attClass) continue;
field.setAccessible(true);
Object value = field.get(slot);
if (value != null) return value;
}
}
return null;
}
private static Field fieldOfType(Class<?> owner, Class<?> type) {
for (Class<?> current = owner; current != null && current != Object.class;
current = current.getSuperclass()) {
for (Field field : current.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers()) || field.getType() != type) continue;
field.setAccessible(true);
return field;
}
}
return null;
}
private static Object firstFieldOfType(Class<?> owner, Object target, Class<?> type) {
for (Class<?> current = owner; current != null && current != Object.class;
current = current.getSuperclass()) {
for (Field field : current.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers()) || field.getType() != type) continue;
field.setAccessible(true);
try {
Object value = field.get(target);
if (value != null) return value;
} catch (Throwable ignored) {
// 下一个
}
}
}
return null;
}
private static Object arrayOfType(Class<?> owner, Object target, Class<?> arrayClass,
Class<?> elementType) {
for (Class<?> current = owner; current != null && current != Object.class;
current = current.getSuperclass()) {
for (Field field : current.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers()) || !arrayClass.isAssignableFrom(field.getType())) {
continue;
}
field.setAccessible(true);
Object array;
try {
array = field.get(target);
} catch (Throwable ignored) {
continue;
}
if (array == null) continue;
for (int i = 0; i < size(arrayClass, array); i++) {
Object element = get(arrayClass, array, i);
if (element != null && elementType.isInstance(element)) return array;
}
}
}
return null;
}
private static void setFloatField(ClassLoader loader, Object target, String name, float value)
throws Exception {
for (Class<?> type = target.getClass(); type != null && type != Object.class;
type = type.getSuperclass()) {
for (Field field : type.getDeclaredFields()) {
if (!field.getName().equals(name) || field.getType() != float.class) continue;
field.setAccessible(true);
field.setFloat(target, value);
return;
}
}
}
private static void set(Object target, String name, Class<?> type, Object value) throws Exception {
for (Class<?> current = target.getClass(); current != null && current != Object.class;
current = current.getSuperclass()) {
for (Field field : current.getDeclaredFields()) {
if (!field.getName().equals(name) || field.getType() != type) continue;
field.setAccessible(true);
field.set(target, value);
return;
}
}
}
private static float floatOf(Object target, String name) {
try {
for (Method method : target.getClass().getMethods()) {
if (!method.getName().equals(name)) continue;
if (method.getParameterTypes().length != 0) continue;
if (method.getReturnType() != float.class) continue;
method.setAccessible(true);
return ((Float) method.invoke(target)).floatValue();
}
} catch (Throwable ignored) {
// 读不到
}
return -1f;
}
private static Object call(Object target, String name, Class<?>[] parameterTypes,
Class<?> returnType, Object... args) throws Exception {
for (Class<?> current = target.getClass(); current != null && current != Object.class;
current = current.getSuperclass()) {
for (Method method : current.getDeclaredMethods()) {
if (!method.getName().equals(name)) continue;
if (!Arrays.equals(method.getParameterTypes(), parameterTypes)) continue;
if (returnType != null && method.getReturnType() != returnType) continue;
method.setAccessible(true);
return method.invoke(target, args);
}
}
throw new NoSuchMethodException(target.getClass().getName() + "." + name);
}
private static Object staticField(Class<?> owner, Class<?> type) {
for (Field field : owner.getDeclaredFields()) {
if (!Modifier.isStatic(field.getModifiers()) || field.getType() != type) continue;
field.setAccessible(true);
try {
Object value = field.get(null);
if (value != null) return value;
} catch (Throwable ignored) {
// 下一个
}
}
return null;
}
private static Method find(Class<?> owner, String name, Class<?>[] parameterTypes,
Class<?> returnType) {
for (Class<?> current = owner; current != null && current != Object.class;
current = current.getSuperclass()) {
for (Method method : current.getDeclaredMethods()) {
if (!method.getName().equals(name)) continue;
if (!Arrays.equals(method.getParameterTypes(), parameterTypes)) continue;
if (returnType != null && method.getReturnType() != returnType) continue;
method.setAccessible(true);
return method;
}
}
return null;
}
private static Method noArgVoid(Class<?> owner, String name) {
for (Method method : owner.getDeclaredMethods()) {
if (!method.getName().equals(name)) continue;
if (method.getParameterTypes().length != 0) continue;
if (method.getReturnType() != void.class) continue;
method.setAccessible(true);
return method;
}
return null;
}
private static int size(Class<?> arrayClass, Object array) {
if (array == null) return 0;
for (Class<?> current = arrayClass; current != null && current != Object.class;
current = current.getSuperclass()) {
for (Field field : current.getDeclaredFields()) {
if (field.getType() != int.class) continue;
field.setAccessible(true);
try {
return field.getInt(array);
} catch (Throwable ignored) {
// 下一个
}
}
}
return 0;
}
private static Object get(Class<?> arrayClass, Object array, int index) {
for (Method method : arrayClass.getMethods()) {
if (!method.getName().equals("D")) continue;
Class<?>[] types = method.getParameterTypes();
if (types.length != 1 || types[0] != int.class) continue;
if (method.getReturnType() != Object.class) continue;
try {
method.setAccessible(true);
return method.invoke(array, Integer.valueOf(index));
} catch (Throwable ignored) {
// 下一个
}
}
return null;
}
}