阶段四收尾:面板两行布局(row/expand/fill)+ 英文输出 + 混淆名对照表
- ViewInstaller:用 Table.row()/Cell.expand()+fill()/setFillParent 排出" 按钮一行、可滚动文字区一行"的面板;修掉把 setDebug 当成 setFillParent 的误用 - Scene2dPanel:按钮 isChecked/setChecked 与 Label.setText 改用对照表定名, 修掉"点一次反复触发"与"文字不刷新"两个 bug - SceneReader:面板输出改英文(编辑器缺中文字形,中文会渲染成方块) - 新增 tools/obfmap/MapMethods.java:载荷 vs 上游 libGDX 的字节码指纹比对工具 - 新增 混淆名对照表.md:类/方法/字段映射、推导办法、已踩的坑、验证状态
This commit is contained in:
1 parent
2e7ab7c33c
commit
8f7b72271f
8 files changed
+677
-52
No files matched your search
@@ -156,24 +156,21 @@ public final class Scene2dPanel {
|
||||
throws Exception {
|
||||
Class<?> buttonClass = button.getClass();
|
||||
Class<?> labelClass = label.getClass();
|
||||
Method setChecked = null;
|
||||
Method isChecked = null;
|
||||
for (Method setter : buttonClass.getMethods()) {
|
||||
if (setter.getParameterTypes().length != 1
|
||||
|| setter.getParameterTypes()[0] != boolean.class
|
||||
|| setter.getReturnType() != void.class) continue;
|
||||
setter.invoke(button, Boolean.TRUE);
|
||||
for (Method getter : buttonClass.getMethods()) {
|
||||
if (getter.getParameterTypes().length != 0
|
||||
|| getter.getReturnType() != boolean.class) continue;
|
||||
if (Boolean.TRUE.equals(getter.invoke(button))) {
|
||||
setChecked = setter;
|
||||
isChecked = getter;
|
||||
break;
|
||||
}
|
||||
// 优先用对照表里定好的名字:Button 上无参方法叫 "String" 的有两个(boolean 和 float),
|
||||
// 只按名字取会取错,必须连返回类型一起判。
|
||||
Method isChecked = findPinned(buttonClass, Obf.BUTTON_IS_CHECKED, boolean.class);
|
||||
Method setChecked = findPinned(buttonClass, Obf.BUTTON_SET_CHECKED, void.class, boolean.class);
|
||||
if (isChecked == null || setChecked == null) {
|
||||
// 名字对不上(换版本)就退回运行期自校准 —— 只在 Button 自己这一层里试,
|
||||
// 不然会扫到继承来的方法,选出一对语义无关的 setter/getter(现象是点击被反复触发)。
|
||||
System.err.println("[panel] 对照表里的 isChecked/setChecked 没找到,改用自校准");
|
||||
Method[] pair = calibrateChecked(button, buttonClass);
|
||||
if (pair == null) {
|
||||
System.err.println("[panel] 没能标定 isChecked/setChecked");
|
||||
return;
|
||||
}
|
||||
setter.invoke(button, Boolean.FALSE);
|
||||
if (isChecked != null) break;
|
||||
isChecked = pair[0];
|
||||
setChecked = pair[1];
|
||||
}
|
||||
if (isChecked == null) {
|
||||
System.err.println("[panel] 没能标定 isChecked/setChecked");
|
||||
@@ -184,7 +181,17 @@ public final class Scene2dPanel {
|
||||
|
||||
final Method readChecked = isChecked;
|
||||
final Method resetChecked = setChecked;
|
||||
final Method setText = findMethod(labelClass, 1, CharSequence.class);
|
||||
// setChecked(boolean, boolean) 的第二个参数是"是否派发事件"。复位时用 false,
|
||||
// 否则把 isChecked 置回 false 这件事本身又会派发一次事件,表现为按一下读两次。
|
||||
final Method resetQuietly = findPinned(buttonClass, Obf.BUTTON_SET_CHECKED_FIRE, void.class,
|
||||
boolean.class, boolean.class);
|
||||
// 必须连返回类型一起判:Label 上 `D(CharSequence)` 有 void(setText)和 boolean
|
||||
// (textEquals)两个,按参数个数找会挑中 textEquals,表现是文字永远不刷新。
|
||||
final Method setText = findPinned(labelClass, Obf.LABEL_SET_TEXT, void.class, CharSequence.class);
|
||||
if (setText == null) {
|
||||
System.err.println("[panel] 没找到 Label.setText,面板文字不会更新");
|
||||
return;
|
||||
}
|
||||
final ClassLoader classLoader = loader;
|
||||
Thread poll = new Thread(new Runnable() {
|
||||
@Override
|
||||
@@ -192,7 +199,11 @@ public final class Scene2dPanel {
|
||||
while (true) {
|
||||
try {
|
||||
if (Boolean.TRUE.equals(readChecked.invoke(button))) {
|
||||
resetChecked.invoke(button, Boolean.FALSE);
|
||||
if (resetQuietly != null) {
|
||||
resetQuietly.invoke(button, Boolean.FALSE, Boolean.FALSE);
|
||||
} else {
|
||||
resetChecked.invoke(button, Boolean.FALSE);
|
||||
}
|
||||
System.err.println("[panel] 按钮被触发,开始读取场景对象");
|
||||
// Actor 自己记着所在 Stage,直接问它就行
|
||||
Object stage = firstFieldOfType(button, STAGE);
|
||||
@@ -229,6 +240,47 @@ public final class Scene2dPanel {
|
||||
}
|
||||
}
|
||||
|
||||
/** 按"名字 + 参数类型 + 返回类型"找方法:混淆后同名方法成堆,少判一个条件就会挑错。 */
|
||||
private static Method findPinned(Class<?> owner, String name, Class<?> returnType,
|
||||
Class<?>... parameterTypes) {
|
||||
for (Method method : owner.getMethods()) {
|
||||
if (!method.getName().equals(name)) continue;
|
||||
if (!java.util.Arrays.equals(method.getParameterTypes(), parameterTypes)) continue;
|
||||
if (returnType != null && method.getReturnType() != returnType) continue;
|
||||
return method;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行期自校准:把每个 boolean setter 设成 true,看哪个无参 boolean getter 跟着变 true。
|
||||
* 只在按钮类自己这一层(含父类)的方法里找 —— 扫到继承链上无关的方法会配出一对假组合,
|
||||
* 现象是"点一次按钮被反复触发"。
|
||||
*/
|
||||
private static Method[] calibrateChecked(Object button, Class<?> buttonClass) throws Exception {
|
||||
for (Class<?> type = buttonClass; type != null && type != Object.class;
|
||||
type = type.getSuperclass()) {
|
||||
for (Method setter : type.getDeclaredMethods()) {
|
||||
if (setter.getParameterTypes().length != 1
|
||||
|| setter.getParameterTypes()[0] != boolean.class
|
||||
|| setter.getReturnType() != void.class) continue;
|
||||
setter.setAccessible(true);
|
||||
setter.invoke(button, Boolean.TRUE);
|
||||
for (Method getter : type.getDeclaredMethods()) {
|
||||
if (getter.getParameterTypes().length != 0
|
||||
|| getter.getReturnType() != boolean.class) continue;
|
||||
getter.setAccessible(true);
|
||||
if (Boolean.TRUE.equals(getter.invoke(button))) {
|
||||
setter.invoke(button, Boolean.FALSE);
|
||||
return new Method[] {getter, setter};
|
||||
}
|
||||
}
|
||||
setter.invoke(button, Boolean.FALSE);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 从 Fx.D(静态 Actor)出发拿 Stage:libGDX 里每个 Actor 都记着自己所在的 Stage。 */
|
||||
private static Object stageFromActorAnchor(ClassLoader loader) {
|
||||
try {
|
||||
|
||||
Reference in new issue
Block a user