- ViewInstaller:用 Table.row()/Cell.expand()+fill()/setFillParent 排出" 按钮一行、可滚动文字区一行"的面板;修掉把 setDebug 当成 setFillParent 的误用 - Scene2dPanel:按钮 isChecked/setChecked 与 Label.setText 改用对照表定名, 修掉"点一次反复触发"与"文字不刷新"两个 bug - SceneReader:面板输出改英文(编辑器缺中文字形,中文会渲染成方块) - 新增 tools/obfmap/MapMethods.java:载荷 vs 上游 libGDX 的字节码指纹比对工具 - 新增 混淆名对照表.md:类/方法/字段映射、推导办法、已踩的坑、验证状态
11 KiB
11 KiB
混淆名对照表(Spine 3.8.99 载荷)
这份文档记录载荷里被混淆的类名/方法名/字段名分别对应什么,以及每一条是怎么推导出来的。 以后要动编辑器内部的 scene2d 控件(加面板、读数据、导出),都先查这里,不要重新猜。
代码里对应的是 viewpanel/src/com/spineview/Obf.java,
里面有同样的常量表,改代码时直接引用。
一、推导办法(可复现)
- 字节码指纹比对:
tools/obfmap/MapMethods.java- 把载荷类与上游 libGDX 同签名方法逐个比对:忽略名字,只比较 "操作码序列 + 常量 + 成员的描述符"(类名按"方法内首次出现的顺序"编号,绕开两边类名不同的问题)。
- 用法:
CP=tools/asm/asm.jar:tools/asm/asm-tree.jar:tools/obfmap/build "$JAVA17" -cp "$CP" MapMethods <上游.class> <载荷.class> # 输出:载荷方法签名 -> 上游方法名(= 唯一匹配,~n/m = 按声明顺序消歧) - 编译:
"$JAVA17" -cp tools/asm/asm.jar:tools/asm/asm-tree.jar -d tools/obfmap/build tools/obfmap/MapMethods.java
- 上游源码核对:拿 libGDX 1.11.0 的源码逐条看方法体语义
(
gdx/src/com/badlogic/gdx/scenes/...),确认指纹给出的结论。 - 运行期验证:面板布局/按钮/文字这些已经在编辑器里真跑过,见文末"验证状态"。
上游版本怎么挑的:Cell/Table/Actor 的字段结构在 1.9.13 / 1.9.14 / 1.11.0 上一致, 指纹命中率也相同,所以用 1.11.0 作为参照(字段类型
Value、Cell.align是Integer)。
二️、最大的坑:同名不同描述符
JVM 允许同一个类里存在"名字相同、描述符不同"的字段和方法,这个载荷把这一招用满了:
dR(Cell)里有 30 多个字段都叫D/B/L/I,靠类型区分 (eN D(Value)、dl D(Actor)、float D、et D(Table)…);et(Table)上D这个名字同时是add(Actor)、align(int)、pad(float)、pad(float×4)…
所以:定位任何成员都必须"名字 + 描述符"一起判,只看名字一定踩雷。已经踩过的两个:
| 踩坑 | 表现 | 原因 |
|---|---|---|
Label 的 D(CharSequence) |
日志打印"读取完成 N 字符",面板文字却一直不刷新 | 有 D(CharSequence)V(= setText)和 D(CharSequence)Z(= textEquals)两个,按"1 个 CharSequence 参数"找会挑到 textEquals |
Table 的 I() |
调到 top() 而不是 row() |
I()LdR; = row()(返回 Cell)、I()Let; = top()(返回 Table),必须判返回类型 |
三、类对照
| 载荷名 | 真实类 | 依据 |
|---|---|---|
dl |
com.badlogic.gdx.scenes.scene2d.Actor |
无父类;do extends dl;有 stage/parent 语义 |
do |
...scene2d.Group |
extends dl;children 是 [Ldl; |
eX |
...ui.WidgetGroup |
extends do;有 setFillParent 语义(见 et.Z(boolean)) |
et |
...ui.Table |
extends eX;有 cells(fI) / rowDefaults(Cell) |
dR |
...ui.Cell |
et.add(Actor) 的返回类型;implements hg(=Poolable) |
dt |
...scene2d.Stage |
uk extends dt |
uk |
Stage 的实现(编辑器实际用的那个) | StageHook 挂在它的构造器上 |
eN |
...ui.Value |
抽象类;Cell 的尺寸/间距字段都是它 |
fI |
com.badlogic.gdx.utils.Array |
Table.cells 的类型 |
fu |
...scene2d.utils.Drawable |
Table.background(Drawable) |
hg |
com.badlogic.gdx.utils.Pool.Poolable |
Cell implements hg |
OG |
编辑器单例持有者 | 静态字段 D = com.esotericsoftware.spine.editor.Editor |
Fx |
静态 Actor 锚点 | 静态字段里有 dl 实例,用来倒推 Stage |
us |
视图枚举(Editor/Preview/Tree/…) |
<clinit> 里建常量、按 MV/ut 分组 |
tD |
编辑器 UI 装配类 | 给每个视图 new 出面板 Actor 的地方 |
四、方法对照
4.1 et = Table
| 载荷签名 | 真实方法 | 依据 / 备注 |
|---|---|---|
D(Ldl;)LdR; |
add(Actor) |
返回 Cell;运行期已用于把按钮/文字区放进面板 |
D([Ldl;)Let; |
add(Actor...) |
返回 Table(varargs 版,别和上面混) |
L()LdR; |
add() |
体 = return add((Actor)null) |
I()LdR; |
row() |
体与上游 Table.row() 逐句一致:判 cells.size、endRow()、invalidate()、换 rowDefaults;不调它所有控件都会挤在第一行 |
D(I)LdR; |
getCell(int) |
不够就补 cell |
B(Ldl;)LdR; |
getCell(Actor) |
遍历 cells 找 actor |
S()LdR; |
getRowDefaults()(行默认值 Cell) |
体 = return rowDefaults |
Z(Z)V |
setFillParent(boolean) |
体 = this.C = bl(纯赋值) |
C(Z)V |
setClip(boolean) |
体 = clip = bl; setTransform(bl); invalidate() |
B(Z)V |
setDebug(boolean) |
体 = debug(bl ? Debug.all : Debug.none);调错会让单元格描上彩色边框(踩过) |
I()Let; |
top() |
返回 Table |
S()Let; |
left() |
|
L()Let; |
center() |
|
C()Let; |
bottom() |
|
Z()Let; |
right() |
|
D(I)Let; |
align(int) |
|
D(F)Let; |
pad(float) |
|
D(FFFF)Let; |
pad(top,left,bottom,right) |
|
B(F)Let; |
padTop(float) |
与 L/I/S(F)Let; = padLeft/padBottom/padRight 一组(同形状,按声明顺序对) |
D(Ljava/lang/String;)Let; |
background(String) |
|
D(Lfu;)Let; |
background(Drawable) |
4.2 dR = Cell
尺寸/间距(参数类型不同,靠描述符区分;都返回 Cell):
| 载荷签名 | 真实方法 |
|---|---|
D(dl)LdR; |
setActor(Actor) |
D(eN,eN)LdR; |
size(Value width, Value height) |
D(F)LdR; |
size(float) |
D(FF)LdR; |
size(float,float) |
D(eN)LdR; / B(eN)LdR; |
width(Value) / height(Value) |
B(F)LdR; / L(F)LdR; |
width(float) / height(float) |
L(eN)LdR; / I(eN)LdR; |
minWidth(Value) / minHeight(Value) |
B(eN,eN)LdR; / S(eN)LdR; |
minSize(Value,Value) / minSize(Value) |
C(eN)LdR; / Z(eN)LdR; |
prefWidth(Value) / prefHeight(Value) |
S(F) / C(F) / Z(F) / V(F) |
minWidth(float) / minHeight(float) / prefWidth(float) / prefHeight(float) |
F(F)LdR; 起连续 5 个 (F)LdR; |
maxWidth / maxHeight / padTop / padLeft / padBottom / padRight(同形状成组,按声明顺序对,未逐条验证) |
V(eN)LdR; / F(eN)LdR; / Code(eN)LdR; |
space(Value) / spaceBottom(Value) / spaceRight(Value) |
Code(F) / SourceFile(F) / StackMapTable(F) / init(F) / LineNumberTable(F) |
space(float) / spaceTop(float) / spaceLeft(float) / spaceBottom(float) / spaceRight(float) |
String(F)LdR; |
pad(float) |
D(FFFF)LdR; |
pad(top,left,bottom,right) |
布局开关(这几条是面板能排好版的关键):
| 载荷签名 | 真实方法 | 依据 |
|---|---|---|
D()LdR; |
fill() |
体 = 两个 float 字段 = 1f(上游 fillX = fillY = onef) |
B()LdR; / L()LdR; |
fillX() / fillY() |
各设一个 float 字段 |
StackMapTable()LdR; |
expand() |
体 = 两个 Integer 字段 = onei(上游 expandX = expandY = onei) |
init()LdR; / LineNumberTable()LdR; |
expandX() / expandY() |
|
F()LdR; |
grow() |
指纹唯一命中 |
Code()LdR; / SourceFile()LdR; |
growX() / growY() |
同形状成组 |
C()LdR; |
left() |
体 = align|8 & ~16 |
S()LdR; |
top() |
体 = align|2 & ~4 |
Z()LdR; |
bottom() |
体 = align|4 & ~2 |
V()LdR; |
right() |
体 = align|16 & ~8 |
I()LdR; |
center() |
体 = align = 1i |
D(I)LdR; |
align(int) |
|
D(Z)LdR; / B(Z)LdR; / D(ZZ)LdR; |
fill/uniform 系(未逐条验证) |
|
D()F,B()F,L()F,I()F |
getMinWidth/getMinHeight/getPrefWidth/getPrefHeight |
|
B()V / L()V / D()V |
row() / clear() / reset() |
位掩码常量(都是 Integer 静态字段,值来自 libGDX Align):
C=1(onei)、Z=1(center)、V=2(top)、F=4(bottom)、Code=8(left)、SourceFile=16(right)。
4.3 com.badlogic.gdx.scenes.scene2d.ui.Button(类名保留,方法名混淆)
| 载荷签名 | 真实方法 |
|---|---|
String()Z |
isChecked()(体 = return isChecked,读第 1 个 boolean 字段) |
I(Z)V |
setChecked(boolean)(指纹唯一命中) |
B(ZZ)V |
setChecked(boolean, boolean) —— 第二参是"是否派发事件",复位时传 false,否则点一次会读两次 |
a_(Z)V |
setDisabled(boolean) |
java()Z |
isDisabled() |
Class()Z / Ljava()Z |
isOver() / isPressed()(体分别是 clickListener.D() / clickListener.B(),两者顺序未逐条验证) |
4.4 com.badlogic.gdx.scenes.scene2d.ui.Label(类名保留,方法名混淆)
| 载荷签名 | 真实方法 | 备注 |
|---|---|---|
D(Ljava/lang/CharSequence;)V |
setText(CharSequence) |
|
D(Ljava/lang/CharSequence;)Z |
textEquals(CharSequence) |
陷阱:长得像 setText,调它文字永远不刷新 |
D()LN; |
getText()(返回 StringBuilder) |
|
D(LabelStyle)V / D()LabelStyle |
setStyle / getStyle |
4.5 uk/dt = Stage、Fx/OG = 入口
| 载荷名 | 真实含义 | 备注 |
|---|---|---|
uk.<init> |
Stage 实现类的构造器 | Stage 在编辑器里只是局部变量,只能钩构造器拿实例 |
Fx 的静态 dl 字段 |
一个静态 Actor | 由它 Actor.stage 反查 Stage(比钩构造器更省事) |
OG.D |
编辑器单例 | com.esotericsoftware.spine.editor.Editor |
us.<clinit> |
视图枚举初始化 | 加新视图必须在 $VALUES 组装之后、分组列表生成之前插 |
五、验证状态
| 结论 | 证据 |
|---|---|
et.D(Ldl;)LdR; = add、et.I()LdR; = row |
面板从"按钮和文字挤在同一行"变成"按钮一行、文字区一行并铺满"(截图已确认) |
dR.C()LdR; = left、dR.D()LdR; = fill、dR.StackMapTable()LdR; = expand |
按钮靠左上、文字区撑满并出现滚动条 |
et.Z(Z)V = setFillParent |
换成它之后面板铺满停靠框(先前误调 setDebug 时只会描出彩色单元格边框) |
Button.String()Z = isChecked、I(Z)V = setChecked |
点击能触发一次读取;用前缀自校准曾配错组合,导致反复触发 |
Label.D(CharSequence)V = setText |
修掉 textEquals 之后面板文字才真正刷新 |
未逐条验证的(结构成组、按声明顺序推定):Cell 的 max/pad 系列、Button 的 isOver/isPressed、
Cell 的 growX/growY。要用到时,先用 MapMethods 重跑一遍,再在编辑器里实测。