目标:在 Spine 编辑器窗口内部加一个自己的视图面板(按钮 + 文字显示), 点击读取场景对象,并像原生面板一样出现在 Views 菜单、可停靠可折叠。 本轮打通: 1. 编辑器在我们自己的进程里完整启动 - 类加载器必须是 URLClassLoader 本身(自检要求) - Editor.main 必须在 AWT EDT 上调用 - jvm.cfg 第 21 行要以两个空格结尾 + -DSpineJava - Editor.D:I 版本号、fonts/、/tzax 资源、Java8 版 launcher.Launcher 替身 2. 3.8.99 全量解密 → 可读源码 - 字符串 17251/17461(伪造同名调用帧对齐 key) - 混淆动态派发还原 53871 处(调引导方法取 MemberName) 3. 自建视图 - agent 在内存里给 us 枚举加 spineView 常量(不改磁盘,避开完整性自检) - 面板 Actor 由外部反射填进 us.spineView.D - 复用已有图标做别名(不新建图片资源) 最终 "Spine View" 出现在 Views 菜单,可与 Tree 并排停靠、折叠展开 另外: - ViewPatcher/ViewAgent:视图枚举与面板挂载的字节码插桩 - DecAll/ObMap/rewrite_source:字符串解密、派发还原、可读源码生成 - 阶段四-视图面板挂载.md:完整记录做法与踩过的坑 不纳入版本管理:rea/(独立仓库)、tools 下的 JDK/Ghidra 工具链、 Spine.app 与 spine-lab 副本、从载荷派生的批量产物。
5.4 KiB
5.4 KiB
ViewPanel 进展与结论(2026-10-15)
目标:在 Spine 4.2.43 编辑器窗口内新增一个面板(一个按钮 + 一块文字),点击后读取编辑器里的对象。
一、已经打通的部分(都有实测证据)
| 环节 | 状态 | 关键证据 |
|---|---|---|
| 注入编辑器进程 | ✅ | DYLD_INSERT_LIBRARIES + _dlsym 重绑定 + 拦 JNI_CreateJavaVM |
| 把我们的类定义进编辑器的类加载器 | ✅ | LPt4.mc(2370 条类名)→ defineIntoLoader → class com.spineview.SceneScan |
| 在编辑器命名空间里按名字找类 | ✅ | s.AmK / s.Xgs / s.nHS / s.heZ / s.fSy / Editor / TextButton 全部找到 |
| 找到编辑器真实 UI 根 | ✅ | s.Xgs 子树含 s.XDB "Editor"/"Preview"/"Weights"/"Tree"、s.nHS "mainMenu" |
| 确认是 Scene2D + Stage(不是 Swing) | ✅ | scene2d 类 33 个;swing/awt 应用类 0;AWT 窗口 0 |
| 拿到渲染线程执行权 | ✅ | 编辑器自己的 Actions.run(Runnable),Action 每帧在渲染线程跑 |
| 在渲染线程上构造出编辑器原生控件 | ✅ | *** 编辑器侧(渲染线程):按钮+文字已挂上 |
二、两个曾经卡了很久的坑(已定位并修掉)
-
编辑器和启动器是两套类加载器。同名类不是同一个 Class:
Class.forName("s.nHS")会抛ClassNotFoundException,而对象的getClass().getName()就是s.nHS。 → 必须把我们的类DefineClass进编辑器的加载器,或至少保证"控件类与样式取自同一个节点"。 -
控件必须在渲染线程上构造。
Label.<init>→getPrefHeight→ 字形生成 →glBindTexture, 没有当前 GL 上下文就是 SIGSEGV(libGL.dylib+0x4bc)。离线建、大栈线程建都不行。附带:样式若从"对话框"里的控件取,会让字体类
s.QjK自递归(<init>↔I无限循环)。 正确做法是从主界面正在渲染的控件上取样式。
三、当前唯一剩下的问题
控件已经建好、也调用了 addActor 挂到编辑器 Stage 根(s.Xgs),但紧接着进程就退出,
而且 spine.log 里没有崩溃报告(只有正常启动输出),说明更像是渲染线程抛异常后被应用自身的错误处理结束掉。
已经排除的:
- 构造阶段(已移到渲染线程,不再 SIGSEGV)
- 挂载点选错(自动扫描挂过 40 个容器,都不显示)
四、下一步的两个方向
- 隔离挂在哪个动作上:只构造不
addActor→ 再只addActor不设文字 → 再设文字,逐步定位是 "绘制我们的控件"还是"改 Stage 结构"导致的退出。 - 换宿主:不挂 Stage 根,改挂到
s.azP(工具条容器)或s.KJW(面板容器), 看是否是 Stage 根不被直接绘制。
五、复现方式
./viewpanel/build.sh
./viewpanel/run.sh /path/to/project.spine # 或 open -n --env ... Spine.app --args -u 4.2.43 <project>
# 日志:/tmp/spine-viewpanel.log
关键代码:
viewpanel/native/inject.c— 注入器;VPC_defineIntoLoader是"定义进编辑器加载器"的入口viewpanel/src/com/spineview/SceneScan.java— 探针、命名空间识别、渲染线程装配
六、最终实验结果(项目在此停止)
| 实验 | 做法 | 结果 |
|---|---|---|
| A | 渲染线程上构造按钮+文字,不挂载 | ✅ 进程存活 —— 构造阶段完全没问题 |
| C | 把界面里已有的 s.nHS 重新 addActor 到同一处 |
❌ 进程退出 —— 不是我们造的控件的问题,是"加子节点"这个动作本身 |
| B | 换宿主 s.azP / s.KJW / s.nHS |
⚠️ 这些类只有类名没有实例,不在可达对象里 |
| D | 从 Editor 实例(com.esotericsoftware.spine.editor.Editor._)出发找 Stage |
❌ 死路:Editor 直接继承 Object,0 个实例字段、只有 3 个静态字段(一个 long、一个 null 的 Object[]、它自己),是空壳 |
结论:注入链路、类命名空间、渲染线程、原生控件构造全部打通;唯一失败的是
「往 s.Xgs 加子节点」这个动作(连挂已有控件也会退出),而 Editor 实例这条路又因为它是空壳而断掉。
还没试过的两条路(留给后续)
- 找到编辑器每帧真正绘制的那棵 UI 根:
s.Xgs很可能只是骨架容器,不是被绘制的那层。 需要继续在 2370 个类名的类表里找"持有s.azP/s.KJW实例的类",从它的静态字段里拿实例。 - 接管一个已有面板:不新增节点,改为把某个现有面板的内容替换成我们的按钮+文字 (阶段一验证过"改现有控件的文字"是会渲染的)。
已确认可用的能力(后续可直接复用)
viewpanel/native/inject.c的VPC_defineIntoLoader:把任意类定义进编辑器加载器LPt4- 类表:
s.LPt4.mc(ConcurrentHashMap,约 2370 个类名),用它能按名字解析出编辑器全部 2500 个类 - 编辑器侧命名空间里可解析:
s.AmK(Actor) /s.Xgs(Group) /s.nHS/s.heZ(Stage) /com.esotericsoftware.spine.editor.Editor/com.badlogic.gdx.scenes.scene2d.ui.TextButton等 - 渲染线程入口:编辑器自己的
Actions.run(Runnable) - 控件构造:必须在渲染线程上;样式必须取自主界面正在渲染的控件(否则字体类
s.QjK自递归)