129 lines
11 KiB
Markdown
129 lines
11 KiB
Markdown
# 阶段五:把面板接到"真实数据"上(对象增删改查 / 属性读取与改写 / 命名还原)
|
||
|
||
本文记录阶段五**已经做成的**、**实测到的数字**、**踩过的坑**,以及**还没做完的**。
|
||
详细的"混淆名 ⇒ 真名"对照在 [`混淆名对照表.md`](混淆名对照表.md),交互链路在 [`编辑器交互与数据API.md`](编辑器交互与数据API.md)。
|
||
|
||
---
|
||
|
||
## 一、阶段五做成了什么
|
||
|
||
### 1. 面板(Spine View)长这样(三段式)
|
||
|
||
| 段 | 内容 | 高度 |
|
||
| --- | --- | --- |
|
||
| 上 | `Refresh / New Bone / New Slot / New BBox / New Path / Delete / Rename / Inspect / Set Value` + 名字输入框 | 由 `spineview-fit` 线程设置(当前按 600 的比例) |
|
||
| 中 | 列表 / Inspector(对象清单 或 选中对象的字段+方法) | 同上(1400 的比例) |
|
||
| 下 | 日志(最近 6 条按钮动作提示) | 同上(400 的比例) |
|
||
|
||
面板文字**一律英文**(编辑器只装了拉丁字形,中文渲染成方块)。
|
||
|
||
### 2. 能做的事(都已实测跑通)
|
||
|
||
| 能力 | 实现 | 证据 |
|
||
| --- | --- | --- |
|
||
| **列出**场景对象 | 走层级树的节点;`SceneObjects.listAllNodes()`(树里 28 个对象:skeleton/bone/slot/attachment + Constraints/Draw Order/… 固定根) | 面板列表与编辑器 Tree 一致 |
|
||
| **选中**对象 | `VZ.D(Tree.Node, boolean)`(`SceneObjects.select`) | 编辑器 Tree 里对应行被高亮(截图已确认) |
|
||
| **读**当前选择 | `VZ.B()` = `Selection.getLastSelected().object` | 面板 "selected: bone \"bone3\"" 与编辑器一致 |
|
||
| **增**对象 | 先选中父级 → 跑编辑器命令 `New Bone` / `New Slot` / `New BBox` / `New Path` | 新建出 `bone7`、`bone8`(骨骼自动编号)等 |
|
||
| **删**对象 | `un.D()` → `对象.L()` → `un.B()` → `VZ.D(骨架,true,true)` 重建树 | 删除后树与面板同步(父骨骼删除会连子骨骼一起删) |
|
||
| **改**名字 | 同上,`对象.D(新名字,true)` + 重建树 | `bone3` → `panel_test` 生效 |
|
||
| **改**属性(测试用) | Inspector 里点字段行 → 值读进输入框 → `Set Value` 直接写模型字段(带撤销) | 写回后值与编辑器属性面板同步 |
|
||
| **输入框自动化** | 有些 `New X` 会弹"输入名字"对话框:找到"带 TextField 且有 `L(Object)V` 回调"的对话框对象 → 填名字 → 直接调 `L(true)`(等于点 OK),不弹给用户 | `[crud] 已自动填写对话框并点 OK` |
|
||
| **跟随编辑器选择** | 后台线程 600ms 轮询 `VZ.B()`,变了就自动 Inspect | 日志 `[panel] Inspected slot "mega_dg6": 48 entries` |
|
||
| **鼠标滚轮** | 点面板按钮后把 Stage 的 scroll focus 抢到列表(`grabScrollFocus`) | 用户实测:"拖动/点列表后滚动条出现、滚轮可用" |
|
||
|
||
### 3. 命名还原(把 `D/B/L` 变成有意义的名字)
|
||
|
||
| 产物 | 内容 |
|
||
| --- | --- |
|
||
| `tools/deobf/Renamer.java` | ASM `Remapper`:按映射表重命名**成员**(不改类名 —— 载荷的 indy 靠加密串里的原名反射查找) |
|
||
| `tools/deobf/anchors.tsv` | 手动/实测锚点(含依据注释) |
|
||
| `tools/deobf/anchors-libgdx.tsv` | `MapMethods` 对上游 **gdx-1.11.0** 的**唯一命中**(`= `)条目 |
|
||
| `tools/deobf/anchors-libgdx-unconfirmed.tsv` | 只给了候选(`~n/m`)的条目 —— **不可信,别用** |
|
||
| `tools/deobf/names.tsv` | 合并结果(当前 **232 条**):确定 + 手动/实测 |
|
||
| `~/Desktop/spine398/src-named/` | 改名后的反编译源码(每个类单独反编译、`class_<载荷名>.java` 命名) |
|
||
| `tools/decrypt/DumpSites.java` | 逐 invokedynamic 站点解出"真实类.方法 **+ 描述符**"(带描述符是关键) |
|
||
|
||
**面板 Inspector 现在会按映射表显示真名**,格式 `真名(混淆名)`,例如:
|
||
|
||
```
|
||
float scaleX(L) = 1.0 float width(C) = 166.0
|
||
float rotation(S) = 0.0 float height(Z) = 87.0
|
||
```
|
||
|
||
### 4. 本轮实测出来的字段语义(都写进了 `anchors.tsv`)
|
||
|
||
| 对象 | 字段 | 含义 | 依据 |
|
||
| --- | --- | --- | --- |
|
||
| `Gp`(骨骼) | `Z/V` | 当前 x/y | 面板 Translate(-168,-116) ↔ Z=-168.00009/V=-116.0 |
|
||
| | `F` / `Code` / `SourceFile` | 当前 rotation / scaleX / scaleY | 面板 Rotate 341.456 ↔ F=33.676704;Scale(0.8799,0.572) ↔ Code/SourceFile |
|
||
| | `StackMapTable` / `LineNumberTable` | 当前 shearX / shearY | 面板 Shear(-52.221,-28.63) |
|
||
| | `D/B/L/I/S/C` | 静止姿态(setup) 的 x/y/rotation/scaleX/scaleY/shear(推断) | 默认值 0,0,0,1,1,0;未改动时与上面那组相等 |
|
||
| | `Class` / `Ljava` / `java` | length / 世界缩放 X / 世界缩放 Y | 面板 Length=0.0 ↔ Class=0.0;Ljava/java 与 Code/SourceFile 相等 |
|
||
| `GO`(附件) | `S/L/I/C/Z` | rotation / scaleX / scaleY / width / height | 面板 Region 49°/1.997/1.335/157/154 一一对应 |
|
||
| `GU`(插槽) | `String D` / `Gp D` / `Gn D` / `Color D` | 名字 / 所属骨骼 / 附件 / 颜色 | 面板 Slot 只有 Name+Color;树节点名一致 |
|
||
| 通用 | `Table` 上 `D(Ldl;)LdR;`=`add`、`I()LdR;`=`row`、`SourceFile()`=`clearChildren` | | 指纹 + 目录铺满/换行实测 |
|
||
|
||
---
|
||
|
||
## 二、踩过的坑(按"代价从大到小")
|
||
|
||
1. **`VZ.D(Object)` 不是"选中"** —— 它内部是 `Selection.L()`(去掉选中项)。真正"设成选中"的是 `VZ.D(Tree.Node, boolean)`。这让我前面所有增删改都"执行了但没生效"。
|
||
2. **动模型的代码必须在编辑器渲染线程** —— `OG.I()` 有线程断言,非渲染线程直接抛 `RuntimeException`;要用编辑器的 `OG.B(Runnable)` 切过去。
|
||
3. **不能在渲染线程里 sleep** —— 编辑器有些创建是**下一帧**做的;占着渲染线程睡 300ms,那一帧永远不来,对象就建不出来。
|
||
4. **`MapMethods` 的候选(`~n/m`)不是结论** —— 我把它们混进 `names.tsv` 当确定名字用,结果"拿的方法不对"。现在拆成 `anchors-libgdx.tsv`(只放 `=` 唯一命中)与 `anchors-libgdx-unconfirmed.tsv`。
|
||
5. **同名不同描述符的坑就在我们自己代码里** —— `Cell.fill()` 的载荷名是 `D`,但 Cell 上有多个无参 `D()`,`getMethod("D")` 会取错;`fillY="L"` 是"按形状猜"的,没验证过(很可能取错)。
|
||
6. **`ScrollPane.B(ZZ)V` 是 `setScrollingDisabled`,不是 `setForceScroll`** —— 我传了 `(false,true)`,等于**把纵向滚动关掉了**(现象:列表滚不动)。`setForceScroll` 是 `I(ZZ)V`。
|
||
7. **滚轮不生效的根因是焦点** —— 给列表 ScrollPane 挂事件监听后,滚轮事件收到 **0 条**:焦点在画布上。解法:点面板后 `Stage.setScrollFocus(面板)`;找这个方法必须用 `getMethods()`(它声明在接口 `LPt9` 里,`getDeclaredMethods` 扫不到)。
|
||
8. **`run.sh` 漏了 `import os`** —— python 那句报 NameError,**编辑器根本起不来**;而我用 `>/dev/null` 把错误吞了,白排查了好几轮。
|
||
9. **`nohup … &` 启动会被会话回收** —— 必须 `subprocess.Popen(..., start_new_session=True)`,否则我这边一次工具调用结束,编辑器就被收掉(你几次看到"进程没了"就是这个)。
|
||
10. **面板根表格的高度被父容器限制** —— `fillParent` 是"复制父容器尺寸",而父容器又按我们的内容算 → 互相依赖;结果 panel 永远等于自己算出来的高度(实测 root≈1360,停靠框 1944)→ 底部固定留一条空带(你说的"区域1")。**从面板内部无解**,只能用 `expand` 或把停靠栏拖大。
|
||
11. **热更(换容器重建面板)把面板搞成空白** —— 那版已 `git revert`;要做得换一种容器(能让 dock 正确布局的 WidgetGroup)。
|
||
12. **macOS 文件系统大小写不敏感** —— 一次反编译整包时 `GP.java`/`Gp.java`、`Yr.java`/`yR.java` 互相覆盖,导致"有些类像没改名"。现在改成每个类单独反编译 + `class_<载荷名>.java`。
|
||
13. **`Table.setDebug(true)` 的红/绿线是调试描边**,不是布局错误;它给每层表格的每个 cell 各画一圈(红=根表格三段,绿=内部格)。
|
||
14. **我自作主张加过"分页"**(你没要求)—— 已回滚,并按你说的把纪律写成项目级技能 `.codex/skills/scope-discipline`。
|
||
|
||
---
|
||
|
||
## 三、API 规模统计(载荷里的"面")
|
||
|
||
| 分组 | 类 | 方法 | public 方法 |
|
||
| --- | --- | --- | --- |
|
||
| LWJGL(保名) | 629 | 9564 | 5444 |
|
||
| Spine 编辑器(保名,`editor/**`) | 57 | 312 | 184 |
|
||
| libGDX(保名那部分) | 52 | 545 | 462 |
|
||
| jglfw + libimagequant | 7 | 169 | 150 |
|
||
| reflectasm + 其它第三方 | 13 | 98 | 55 |
|
||
| **默认包(类名被混淆)** | **3418** | **15782** | **9918** |
|
||
| 合计 | 4176 | 26470 | 16213 |
|
||
|
||
默认包那 3418 个是**混合体**:用"方法描述符多重集 + 上游 gdx-1.11.0 指纹"能较有把握认出 **~55 类 / ~907 方法**属于 libGDX(你举的"设置文字/颜色"在这层),**其余 ~3363 类 / ~14875 方法**是编辑器自有业务代码(Tree 视图、模型、命令系统、属性面板、导出…)。这只是"正例识别",真实环境自带会比 907 略多。
|
||
|
||
另外:invokedynamic 调用点 **55637** 个(`~/Desktop/spine398/sites.tsv`,已解出真实目标+描述符);编辑器注册的菜单命令 100 多条(`hotkeys.txt`)。
|
||
|
||
---
|
||
|
||
## 四、怎么跑(当前唯一的正确入口)
|
||
|
||
```bash
|
||
cd "/Users/tianmokeji/Desktop/临时/spine加功能"
|
||
JAVA_HOME=/tmp/jdk-17.0.20.1+1/Contents/Home ./viewpanel/build.sh # 编译
|
||
./viewpanel/run.sh # 先杀旧实例再启动(约 20s)
|
||
# 或双击 viewpanel/重启编辑器.command / spine-lab/启动实验室Spine.command(后者已修好,内部同样调 run.sh)
|
||
tail -f /tmp/spineview.log # 日志([panel]/[view]/[crud] 前缀)
|
||
```
|
||
|
||
调试开关:`-Dspine.probe=objects|panels|select|crud|matrix|panel`(探针)、`-Dspine.runCommand="New Bone"`(跑一条编辑器命令)、`-Dpanel.h=<像素>`(写死面板高度,默认 auto=读 Stage)。
|
||
|
||
---
|
||
|
||
## 五、还没做完 / 没验证的(别当成已完成)
|
||
|
||
1. **"列表控件铺满红框"**:要裁剪+滚动就不能铺满(ScrollPane 里 widget 按自己内容大小);要铺满就得去掉 ScrollPane(会溢出)。两条路都试过,**取舍未定**。
|
||
2. **新确认的 `fillY="L"`、`CELL_FILL="D"`**:`D` 在映射表里是唯一命中的 `fill`,`L` 从没验证过 —— 列表没被纵向拉伸很可能就是这里取错了方法。
|
||
3. **编辑器自有类(那 3363 个)的名字还原**:只做了 libGDX 层与少数手工锚点;业务层(`VZ`/`GP`/命令系统…)还没命名。
|
||
4. **热更**:回滚了;要做就得换容器方案。
|
||
5. **`GO/GU` 里还有一批未确认字段**(`boolean D/B/L/I/S/C/Z/V/F/Code`、`int D`、`Color B/L/I`…)。
|
||
6. **调试描边常开**(`ObjectPanel` 里搜 `TABLE_SET_DEBUG` 可关),还没做开关。
|
||
7. **导出链路**:现阶段未动(`Export` 菜单动作可用,见 `编辑器交互与数据API.md` §6)。
|