Files
Spine_lab/开发总纲领.md
T
tianmo 2e7ab7c33c 阶段四: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 副本、从载荷派生的批量产物。
2026-10-16 13:34:18 +08:00

115 lines
7.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`