package com.spineview; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; /** * 用**编辑器自己的命令系统**执行菜单动作 —— 增删改查都从这里走。 * * 链路(都是从反编译源码里读出来的,见 `混淆名对照表.md`): * ``` * com.esotericsoftware.spine.editor.Editor (OG.D 里的单例) * └─ public pE D; ← 命令 / 热键系统(Editor. 里 new) * ├─ public final void D(String) ← 按名字执行(名字就是 hotkeys.txt 里的名字) * └─ public final pC D(String) ← 按名字取菜单项(不执行) * ``` * 可用的名字直接看 `~/Library/Application Support/Spine/hotkeys.txt`,例如: * `New Bone` / `New Slot` / `New Bounding Box` / `New Path` / `New Clipping` / * `New IK Constraint` / `New Animation` / `New Event` / `New Folder` / * `Delete` / `Rename` / `Duplicate` / `Cut` / `Copy` / `Paste` / `Set Parent` / * `Draw Order Up` / `Draw Order Down` / `Visibility` / `Expand` / `Collapse` … * * 注意:这些动作都作用在**编辑器当前的选择**上(Delete/Rename/Duplicate 等), * 所以后续要先能设置选择(Tree 的选中项),才谈得上"对着哪个对象增删改"。 */ public final class Commands { /** 命令系统的载荷类名(CFR 把它重命名成 pe_0,真实类名是 pE)。 */ private static final String COMMANDS = "pE"; private Commands() { } /** 从编辑器单例上取命令系统对象(Editor 的实例字段)。 */ public static Object find(ClassLoader loader) throws Exception { Object editor = SceneReader.findEditor(loader); if (editor == null) return null; for (Class type = editor.getClass(); type != null && type != Object.class; type = type.getSuperclass()) { for (Field field : type.getDeclaredFields()) { if (Modifier.isStatic(field.getModifiers())) continue; if (!field.getType().getName().equals(COMMANDS)) continue; field.setAccessible(true); Object value = field.get(editor); if (value != null) return value; } } return null; } /** * 执行一条命令(名字取自 hotkeys.txt / 菜单项)。 * 返回 false 表示没找到命令或执行抛了异常(异常已经打进日志)。 */ public static boolean run(ClassLoader loader, String name) { try { Object commands = find(loader); if (commands == null) { System.err.println("[cmd] 没找到命令系统(未拿到 Editor 实例?)"); return false; } // pE 上有一个参数的 String 方法有四个(返回 pC / void / ArrayList / String), // 只有返回 void 的那个是"执行"。 Method execute = null; for (Method method : commands.getClass().getMethods()) { if (!method.getName().equals("D")) continue; if (method.getParameterTypes().length != 1) continue; if (method.getParameterTypes()[0] != String.class) continue; if (method.getReturnType() != void.class) continue; execute = method; break; } if (execute == null) { System.err.println("[cmd] 命令系统上没找到 void D(String)"); return false; } System.err.println("[cmd] 执行命令: " + name); execute.setAccessible(true); execute.invoke(commands, name); System.err.println("[cmd] 命令完成: " + name); return true; } catch (Throwable error) { System.err.println("[cmd] 命令失败: " + name + " -> " + error); error.printStackTrace(System.err); return false; } } }