阶段四:Spine 3.8.99 编辑器里挂上自建视图面板

目标:在 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 副本、从载荷派生的批量产物。
This commit is contained in:
tianmo committed 2026-10-16 13:34:18 +08:00
commit 2e7ab7c33c
113 files changed
+25327

No files matched your search

+114
View File
@@ -0,0 +1,114 @@
# Spine 粒子系统 · 开发总纲领
## 一、终极目标(唯一目标)
**在 Spine 编辑器内部新增一个"粒子窗口"**,用于调整 Spine 中**骨骼 / 插槽 / 附件对象的行为**:
- 在编辑器里选中某个骨骼、插槽或附件,为它配置粒子发射行为;
- 粒子的位置、旋转、缩放跟随该对象在动画中的实际运动;
- 最终导出为**Spine 自身可以使用的动画数据**(与 Spine 的导出流程兼容);
- 体验上要像 Spine 自带面板(权重 / 层级树 / 预览)一样,出现在编辑器窗口内部。
> 不是独立 app,不是外挂工具,不是浏览器页面。**必须是在 Spine 编辑器窗口内部的窗口/面板。**
## 二、硬性约束
1. **不破坏 Spine 原有功能**:不改动其原有面板、菜单、导出逻辑的行为;
2. **导出兼容**:新增功能产生的数据必须是 Spine 能识别/导入的形式(Spine JSON / .spine 工程);
3. **用户真实安装不被修改**:`/Applications/Spine.app` 与 `~/Library/Application Support/Spine` 保持原样;
所有试验在副本与隔离数据目录中进行。
## 三、技术现实(已实测确认)
目标 App 是 Java + libGDX(Scene2D UI)+ LWJGL/GLFW/OpenAL 的桌面程序,且这份包被二次打包并加固:
| 事实 | 影响 |
| --- | --- |
| `launcher.jar` 实际未被使用 | 改它无效 |
| 启动类/编辑器类内嵌在 `libjvm`(约 49MB 数据段,加密) | 需要绕过其类来源 |
| JVMTI agent 加载被裁(`-agentpath` 无效) | 常规 agent 方案不可用 |
| classpath 注入无效(自定义类加载器 `s.LPt4`) | 需要 JNI 注入 |
| 类被混淆(libGDX 核心类 → `s.*` 包) | 只能按结构/签名识别 |
| 少量反射/JNI 操作稳定,**大量循环遍历会使其崩溃** | 必须"小步定向"操作 |
| 改写编辑器内部对象可能导致 fatal error | 不能粗暴改内部状态 |
| 面板没有运行期注册 API(只有布局类 `s.oxX`) | 无法"注册"新面板 |
**已打通的能力**:通过 `DYLD_INSERT_LIBRARIES` + fishhook 重绑定 `_dlsym` 拦截 `JNI_CreateJavaVM`,
再用 JNI `DefineClass` 把我们的类注入 JVM 并执行;可枚举全部类(2365 个)、读取任意对象字段、
修改现有控件并让界面真实生效(工具栏按钮文字已验证)。
## 四、路线与现状
| 路线 | 状态 |
| --- | --- |
| 官方插件 / 脚本 API | 不存在 |
| JVMTI agent / javaagent | 被运行时裁掉,不可用 |
| 改 `launcher.jar` / classpath | 无效(未被使用) |
| 直接改编辑器内部对象加新控件(`addActor`) | **不渲染** |
| 改写编辑器内部对象(面板标签等) | 会触发 fatal error,不可取 |
| 在编辑器窗口内自绘(hook `glfwSwapBuffers` + OpenGL) | **实测失败**:即便保存/恢复 GL 状态并保留原有 swap 调用,仍会导致编辑器画面全黑。原因是该编辑器的呈现依赖自身 GL 管线状态,外挂式清屏/绘制会破坏其呈现。已回滚 |
| 独立面板窗口 | 已实现(贴靠编辑器右侧),但**不符合终极目标**,仅作过渡 |
## 五、下一步(按优先级)
1. **窗口内集成的新方向**(旧的"外挂式 GL 自绘"已实测失败并回滚)
- 方向 A:**hook 编辑器自身的绘制调用**(它用 libGDX/LWJGL 走 OpenGL),在它自己的绘制流程里插入我们的绘制,
而不是在帧末清屏自绘 —— 这样不会破坏它的呈现管线;
- 方向 B:把编辑器界面**整帧渲染到离屏缓冲**再合成(复杂度高,暂不考虑);
- 方向 C:改为扩展它自己的 UI 数据(面板集合是编译期固定的,需要更深的逆向)。
2. 面板内容先做占位(列表 + 参数条 + 按钮),确认"窗口内可见、可点击";
3. 接入真实数据:读取 Spine 当前选中的骨骼/插槽/附件(可用已验证的对象读取能力),
把它的动画运动导出为粒子参数;
4. 导出链路:面板产出 Spine JSON → 用已验证的 `Spine --import` 流程导入编辑器。
## 六、✅ 已打通:窗口内 UI 注入(AppKit 叠加方案)
**方法**:我们的 dylib 运行在 Spine 进程内,直接用 **AppKit** 往编辑器窗口的 `contentView` 里
叠加一个原生 `NSView` 面板(真实 `NSButton` / `NSSlider` / `NSTextField`)。
- 不碰 OpenGL,因此**不会破坏编辑器渲染**(对比:帧末 GL 自绘会导致全黑,已废弃);
- UI 位于编辑器窗口**内部**,可随窗口缩放(`autoresizingMask`),与编辑器面板观感一致;
- 实测已在 Spine 编辑器窗口右侧成功显示「粒子系统」面板(标题 + 两个按钮 + 五条参数滑条)。
实现位置:`native-hook/ui.m`(AppKit 面板)+ `native-hook/hook.c`(JVM 建好后延迟注入)。
**下一步:与场景对象交互**
1. 在面板里显示"当前选中的骨骼 / 插槽 / 附件"——通过已验证的 JNI 反射能力读取编辑器当前选择;
2. 面板参数(发射速率等)改变时,写入我们自己的粒子数据结构;
3. 导出:产出 Spine JSON → 交给 `Spine --import` 变成编辑器可用的工程/动画数据。
## 七、相关文档
### 阶段二(本轮)· 面板路线 —— 停止「往它内部塞/掏」
阶段一证明了「在编辑器内部新增面板」在加固运行时上代价过高:`addActor` 不渲染、
改内部控件触发 fatal error、没有运行期注册 API。因此阶段二把力量全部放到**已经跑通的那条线**上:
1. **面板可拖动 / 可折叠**,不遮挡编辑器右侧面板 —— 已实现,并已挂进编辑器窗口(`native-hook/ui.m`);
2. **面板内自有粒子数据模型**(26 项参数、多发射器、JSON 落盘)—— 已实现(`panel_model.{h,m}`),
只动我们自己的结构,不碰编辑器对象;
3. **定向少量读取**当前选中的骨骼/插槽/附件 —— 通道与硬上限已实现(`selection.c`),
还差「编辑器类加载器」的抓取(当前读不到时面板会明说,目标名也可手填);
4. **导出**:面板产出 Spine JSON → `Spine --import` 生成 `.spine` —— 端到端已跑通
(`panel_import.m`,命令行与已验证的连接器一致)。
> ⚠️ 本轮还定位出阶段一遗留的一个坑:`defineClass1` 拦截 + `DefineClass` 打补丁
> (经典 dump 类字节码那套钩子)会让这套加固运行时在启动阶段 SIGSEGV。
> 它只是诊断功能,现已**默认关闭**(`SPINE_HOOK_DUMP_CLASSES=1` 才打开),
> 关掉后编辑器启动正常、面板如期出现。
细节、实测证据与自测方法见 `阶段二-面板实现.md`。
### 面板集合深挖进展(最新)
- 面板内容类 `s.fOM`(基类 `s.Erk` → Group),面板标签页 `s.ILX`/`s.XDB`(ImageTextButton),
标签页里存的是**英文键名**(如 `"Outline"`),中文是本地化渲染出来的;
- 尝试在运行期对象图里找"装着多个 `s.fOM` 的容器(Map/集合)"→ **未找到**:
说明面板不是放在一个可枚举的 Java 集合里,而是被**直接字段引用**(很可能在停靠管理器 `s.oxX` 或编辑器根对象里);
- 下一步:定位 `s.oxX`(有 `_(Erk, boolean, boolean, float…)` 布局方法的类)的实例,dump 它的字段,
找出面板引用列表 → 判断能否在运行期扩展面板集合。
- 技术分析细节:`阶段一分析.md`
- 注入器实现:`native-hook/hook.c`
- 引导类(JNI 注入入口):`spine-lab/build/plugin-src/com/spineparticle/Boot.java`