目标:在 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 副本、从载荷派生的批量产物。
86 lines
5.4 KiB
Markdown
86 lines
5.4 KiB
Markdown
# 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 每帧在渲染线程跑 |
|
||
| **在渲染线程上构造出编辑器原生控件** | ✅ | `*** 编辑器侧(渲染线程):按钮+文字已挂上` |
|
||
|
||
## 二、两个曾经卡了很久的坑(已定位并修掉)
|
||
|
||
1. **编辑器和启动器是两套类加载器**。同名类不是同一个 Class:
|
||
`Class.forName("s.nHS")` 会抛 `ClassNotFoundException`,而对象的 `getClass().getName()` 就是 `s.nHS`。
|
||
→ 必须把我们的类 `DefineClass` 进编辑器的加载器,或至少保证"控件类与样式取自同一个节点"。
|
||
|
||
2. **控件必须在渲染线程上构造**。`Label.<init>` → `getPrefHeight` → 字形生成 → `glBindTexture`,
|
||
没有当前 GL 上下文就是 SIGSEGV(`libGL.dylib+0x4bc`)。离线建、大栈线程建都不行。
|
||
|
||
附带:样式若从"对话框"里的控件取,会让字体类 `s.QjK` 自递归(`<init>` ↔ `I` 无限循环)。
|
||
正确做法是从**主界面正在渲染的控件**上取样式。
|
||
|
||
## 三、当前唯一剩下的问题
|
||
|
||
控件已经建好、也调用了 `addActor` 挂到编辑器 Stage 根(`s.Xgs`),**但紧接着进程就退出**,
|
||
而且 `spine.log` 里**没有崩溃报告**(只有正常启动输出),说明更像是渲染线程抛异常后被应用自身的错误处理结束掉。
|
||
|
||
已经排除的:
|
||
- 构造阶段(已移到渲染线程,不再 SIGSEGV)
|
||
- 挂载点选错(自动扫描挂过 40 个容器,都不显示)
|
||
|
||
## 四、下一步的两个方向
|
||
|
||
1. **隔离挂在哪个动作上**:只构造不 `addActor` → 再只 `addActor` 不设文字 → 再设文字,逐步定位是
|
||
"绘制我们的控件"还是"改 Stage 结构"导致的退出。
|
||
2. **换宿主**:不挂 Stage 根,改挂到 `s.azP`(工具条容器)或 `s.KJW`(面板容器),
|
||
看是否是 Stage 根不被直接绘制。
|
||
|
||
## 五、复现方式
|
||
|
||
```zsh
|
||
./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 实例这条路又因为它是空壳而断掉。
|
||
|
||
### 还没试过的两条路(留给后续)
|
||
|
||
1. **找到编辑器每帧真正绘制的那棵 UI 根**:`s.Xgs` 很可能只是骨架容器,不是被绘制的那层。
|
||
需要继续在 2370 个类名的类表里找"持有 `s.azP` / `s.KJW` 实例的类",从它的静态字段里拿实例。
|
||
2. **接管一个已有面板**:不新增节点,改为把某个现有面板的内容替换成我们的按钮+文字
|
||
(阶段一验证过"改现有控件的文字"是会渲染的)。
|
||
|
||
### 已确认可用的能力(后续可直接复用)
|
||
|
||
- `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` 自递归)
|