Files
Spine_lab/开发总纲领.md
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

7.5 KiB
Raw Permalink Blame History

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