场景对象增删改查调研:命令系统入口 + 新建对象的真实落点

- Commands.java:Editor -> pE(命令系统) -> void D(String) 按名字执行命令
- Launcher:新增 -Dspine.runCommand="<命令名>" 便于验证
- SceneReader:Tree/选中项探针(actor 树按 SnapshotArray 结构遍历)
- 混淆名对照表.md:补命令系统一节、可执行命令名单、skeleton/root 删除边界、
  以及 New X 最终落到静态 KP.B(target, 类型名) 的结论与待办
This commit is contained in:
tianmo committed 2026-10-16 14:20:45 +08:00
1 parent 8f7b72271f
commit a0080428f9
4 files changed
+394 -7

No files matched your search

+88
View File
@@ -0,0 +1,88 @@
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.<init> 里 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;
}
}
}