调试MIUI天气,解除语言限制

调试MIUI天气,解除语言限制

前段时间将系统语言切换至英文后,发现常用的天气云图无法打开,气象预警也不显示,如果通过xposed模块单独将天气调至中文显得十分割裂,正好借此机会学一下安卓逆向

工具

手机上可以使用MT管理器对apk进行解包编辑,并且使用auto.js的悬浮窗调试工具对程序布局进行解析

之前已经装过MagiskHide Props Config,因此直接用它设置系统ro.debuggable打开全局调试,再使用DDMS读取程序的log、跟踪函数调用栈

电脑上则使用了dex-tools(dex2jar)、以及cfr对程序进行反编译,使用vscode在java中定位到问题后再修改smali代码。

过程

无法打开云图

由动态调试代码,对比英文状态下与中文下不同

英文下

1
2
3
4
5
6
7
8
9
07-31 10:52:24.618: D/Wth2:MajesticCloud(19455): afterFrictionValue 0.0
07-31 10:52:24.622: D/Wth2:WeatherScrollView(19455): mDailyForecastView bottom canSee:true
07-31 10:52:24.734: D/Wth2:MajesticWeather(19455): go_touch_move: false
07-31 10:52:24.734: D/Wth2:MajesticWeather(19455): go_touch_move: com.miui.weather2.majestic.detail.MajesticBackSunny@ba7ad23
07-31 10:52:24.735: D/Wth2:MajesticCloud(19455): startTouchAnim:
07-31 10:52:24.735: D/Wth2:MajesticCloud(19455): startTouchAnim
07-31 10:52:24.735: D/Wth2:MajesticCloud(19455): touch up is -1400.0
07-31 10:52:24.761: D/Wth2:MajesticCloud(19455): afterFrictionValue 0.0
07-31 10:52:24.781: D/Wth2:MajesticCloud(19455): afterFrictionValue 0.0

中文下

1
2
3
4
5
6
7
8
9
10
11
12
07-31 01:28:55.907: D/Wth2:MajesticCloud(29817): afterFrictionValue 0.0
07-31 01:28:55.909: D/Wth2:WeatherScrollView(29817): mDailyForecastView bottom canSee:true
07-31 01:28:55.962: D/Wth2:MajesticWeather(29817): go_touch_move: false
07-31 01:28:55.962: D/Wth2:MajesticWeather(29817): go_touch_move: com.miui.weather2.majestic.detail.MajesticBackNightSunny@d3e8e9a
07-31 01:28:55.962: D/Wth2:MajesticCloud(29817): startTouchAnim:
07-31 01:28:55.962: D/Wth2:MajesticCloud(29817): startTouchAnim
07-31 01:28:55.962: D/Wth2:MajesticCloud(29817): touch up is -1400.0
07-31 01:28:55.964: D/Wth2:Navigator(29817): gotoActivityMinuteRain
07-31 01:28:55.966: I/Timeline(29817): Timeline: Activity_launch_request time:817503
07-31 01:28:55.983: D/Wth2:AnalyzeTransferManager(29817): canAnalyzeByKey event: minute_rain_click, result: true
...
07-31 01:28:55.993: D/Wth2:MajesticCloud(29817): afterFrictionValue 0.0

可以观察到从Navigator(29817): gotoActivityMinuteRain开始产生了不同,定位到log gotoActivityMinuteRain的函数,并动态调试记录其调用栈,定位到com.miui.weather2.view.onOnePage.WeatherAqiMinuteView.c(View view),找到问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public /* synthetic */ void c(View view) {
if (this.x != null && c1.t(this.getContext())) {
o0.a(this.getContext(), this.x, -1, false, 1);
r0.a("minute_rain_click"); // 该处log能对应到中文状态下log的不同
}
}
// com.miui.weather2.tools.c1
public static boolean t(Context context) {
boolean bl = !Build.IS_INTERNATIONAL_BUILD && c1.r(context);
return bl;
}
public static boolean r(Context context) {
return c1.f(context).equals(Locale.SIMPLIFIED_CHINESE);
}

因此只要在mt管理器中将该处条件跳转去除即可

1
2
3
4
5
6
7
8
9
10
11
invoke-static {p1}, Lcom/miui/weather2/tools/c1;->t(Landroid/content/Context;)Z

move-result p1

if-nez p1, :cond_e

# goto :cond_1f

.line 2
:cond_e
invoke-virtual {p0}, Landroid/view/ViewGroup;->getContext()Landroid/content/Context;

云图中提示空白

虽然强行打开了云图,但是提示文字显示为空白。这里看日志没能找到不同,只能老老实实看代码定位问题

观察代码分析出com.miui.weather2.w.f$c中函数MinuteRainData a(Context object, CityData object2)获取了CityDataLocale,并且作为参数传入至com.miui.weather2.a0.a.a函数中请求数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private MinuteRainData a(Context object, CityData object2) {
if (this.a) {
WeatherData weatherData = i.a(object, (CityData)object2);
j1.a(object, weatherData, false, ((CityData)object2).isFirstCity());
object = weatherData != null ? weatherData.getMinuteRainData() : null;
} else {
String string2 = this.e; // 实为Context类,反编译错误
String string3 = this.d;
String string4 = ((CityData)object2).getLocale();
String string5 = ((CityData)object2).getExtra();
object2 = ((CityDataLight)object2).isLocationCity() ? "true" : "false";
// 获取的各个string参数请求天气数据
// com.miui.weather2.a0.a.a中有Log信息为getAllWeatherJson
object = com.miui.weather2.z.c.a(com.miui.weather2.a0.a.a(string2, string3, string4, string5, object, (String)object2), System.currentTimeMillis(), object);
}
return object;
}

猜测是英文Locale下返回的数据中包含的提示信息为空,这里改用中文代替
因此尝试将该处string4修改为常量zh_cn,至于为什么不是zh-cn等值是因为CityData底下用到了这个常量(

1
2
3
4
5
invoke-virtual {p2}, Lcom/miui/weather2/structures/CityData;->getLocale()Ljava/lang/String;

move-result-object v2

const-string v2, "zh_cn"

经过修改,已经能正确显示提示信息了

预警卡片不显示

同样不好通过log定位问题,继续读代码(

定位到com.miui.weather2.tools.j1.a(String var0, String var1_4, Context var2_8)中,有一个c1.r对设备Locale进行了判断,直接将返回值设为1

1
2
3
4
5
6
7
8
9
10
11
public static ArrayList<Alert> a(String var0, String var1_4, Context var2_8) {
block15: {
block16: {
block14: {
block17: {
var3_9 = c1.r(var2_8);
var4_10 = null;
var5_11 = null;
if (!var3_9) {
return null;
}
1
2
3
4
5
6
.line 293
invoke-static {p2}, Lcom/miui/weather2/tools/c1;->r(Landroid/content/Context;)Z

move-result v1

const/4 v1, 0x1

可以看到气象预警的框框已经能够正常显示,图标也正常,但是标题不显示

预警卡片标题

被迫学会打log才找到这个问题,中间甚至抓了几次包对比。抓包可以看出。response中并没有诸如”暴雨蓝色预警:南京气象局提醒…“的字样,可以猜测该标题为在客户端中拼接,其中中文冒号“:”出现在代码中都是log部分。如果熟悉安卓开发,应该直接意识到是在资源文件中的格式化字符串拼接的了,可惜不熟悉花了不少时间。

com.miui.weather.view.onOnePage.VerticalCarousel.a(Alert alert)中,使用了Context.getString()获取xml资源文件中的字符串,并通过其格式化。而该资源与Locale绑定,因此切换英文后会返回Null。将该函数改为使用字符串拼接获取输出内容即可,直接用smali写一个StringBuilder拼接即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
.method private a(Lcom/miui/weather2/structures/Alert;)Ljava/lang/String;
.registers 7

.line 22
invoke-virtual {p1}, Lcom/miui/weather2/structures/Alert;->getType()Ljava/lang/String;

move-result-object v0

.line 23
invoke-virtual {p1}, Lcom/miui/weather2/structures/Alert;->getLevel()Ljava/lang/String;

move-result-object v1

.line 24
const-string v2, "\u9884\u8b66\uff1a"

.line 25

invoke-virtual {p1}, Lcom/miui/weather2/structures/Alert;->getDetail()Ljava/lang/String;

move-result-object v3

.line 26

new-instance v4, Ljava/lang/StringBuilder;

invoke-direct {v4}, Ljava/lang/StringBuilder;-><init>()V

.line 27

invoke-virtual {v4,v0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

.line 28

invoke-virtual {v4,v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

.line 29

invoke-virtual {v4,v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

.line 30

invoke-virtual {v4,v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

.line 31

invoke-virtual {v4}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

move-result-object p1

.line 32

return-object p1
.end method

预警详情标题

这里的“暴雨蓝色预警”字样同样没出现在response当中,因此猜测与预警卡片中标题的原因一致。这里由上一个问题的经验,通过auto.js的悬浮窗调试功能,获取该TextViewID:2131361878,直接全局搜索2131361878即可定位到代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public a(View view) {
super(view);
this.y = (TextView)view.findViewById(2131361878);
this.z = (ImageView)view.findViewById(2131361876);
this.A = (TextView)view.findViewById(2131361874);
this.B = (TextView)view.findViewById(2131361875);
this.x = (LinearLayout)view.findViewById(2131361903);
}

...
textView.setText((CharSequence)activityAlertDetail.getString(2131820591, new Object[]{string2, alert.getLevel()}));
((l)((d)b.a(ActivityAlertDetail.this).a(alert.getIconUrl()).a((n)c.e())).b((Drawable)null)).a(this.z);
this.A.setText((CharSequence)a1.c(alert.getPubTimeNum((Context)ActivityAlertDetail.this), (Context)ActivityAlertDetail.this));
this.B.setText((CharSequence)alert.getDetail());

同样用StringBuilder修改掉getString即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
.line 4
invoke-virtual {p1}, Lcom/miui/weather2/structures/Alert;->getType()Ljava/lang/String;

move-result-object v2

.line 5
invoke-virtual {p1}, Lcom/miui/weather2/structures/Alert;->getLevel()Ljava/lang/String;

move-result-object v3

.line 6
const-string v4, "\u9884\u8b66"

.line 7
new-instance v5, Ljava/lang/StringBuilder;

invoke-direct {v5}, Ljava/lang/StringBuilder;-><init>()V

.line 8
invoke-virtual {v5, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

.line 9
invoke-virtual {v5, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

.line 10
invoke-virtual {v5, v4}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

.line 11
invoke-virtual {v5}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

move-result-object v1

.line 12
invoke-virtual {v0, v1}, Landroid/widget/TextView;->setText(Ljava/lang/CharSequence;)V

const/4 v5, 0x0

这里最后一行将v5置零,否则会触发报错。原代码中将v5置零不仅在这一段代码中使用,后面也用于if的参数

1
com.miui.weather2.ActivityAlertDetail$a$a.c(int) failed to verify: void com.miui.weather2.ActivityAlertDetail$a$a.c(int): [0x93] args to 'if' (Precise Reference: java.lang.StringBuilder,Integer) must be integral (declaration of 'com.miui.weather2.ActivityAlertDetail$a$a' appears in base.apk)
两张图

调试MIUI天气,解除语言限制
https://xanderc.top/2022/07/30/debug-mi-weather/
作者
XanderC
发布于
2022年7月30日
许可协议