From 7079053ddeaa1f39f38a485ab9c3a32eee3864fc Mon Sep 17 00:00:00 2001 From: agent Date: Fri, 24 Jul 2026 11:09:44 +0000 Subject: [PATCH] =?UTF-8?q?feat(tools):=20fw=5Finfo/fw=5Fpack=20=E9=95=9C?= =?UTF-8?q?=E5=83=8F=E8=A7=A3=E6=9E=90=E9=87=8D=E6=89=93=E5=8C=85=EF=BC=88?= =?UTF-8?q?CRC32=E9=AA=8C=E8=AF=81+=E5=AD=97=E8=8A=82=E7=BA=A7=E5=BE=80?= =?UTF-8?q?=E8=BF=94=EF=BC=89=EF=BC=8C112=20=E6=B5=8B=E8=AF=95=E5=85=A8?= =?UTF-8?q?=E7=BB=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- analysis/apk/decomp/a9_b0.java | 175 ++++++++++ analysis/apk/decomp/a9_v2.java | 281 ++++++++++++++++ analysis/apk/decomp/c9_m.java | 308 ++++++++++++++++++ ..._ble_BleManager$startDeviceInfoRead$1.java | 83 +++++ pyproject.toml | 2 +- tests/test_fw.py | 64 ++++ tools/__init__.py | 0 tools/fw.py | 54 +++ tools/fw_info.py | 10 + tools/fw_pack.py | 11 + 10 files changed, 987 insertions(+), 1 deletion(-) create mode 100644 analysis/apk/decomp/a9_b0.java create mode 100644 analysis/apk/decomp/a9_v2.java create mode 100644 analysis/apk/decomp/c9_m.java create mode 100644 analysis/apk/decomp/qbsg_top_ble_BleManager$startDeviceInfoRead$1.java create mode 100644 tests/test_fw.py create mode 100644 tools/__init__.py create mode 100644 tools/fw.py create mode 100644 tools/fw_info.py create mode 100644 tools/fw_pack.py diff --git a/analysis/apk/decomp/a9_b0.java b/analysis/apk/decomp/a9_b0.java new file mode 100644 index 0000000..c7e65a9 --- /dev/null +++ b/analysis/apk/decomp/a9_b0.java @@ -0,0 +1,175 @@ +package a9; +public final synthetic class b0 implements a7.c { + public final synthetic int c; + public final synthetic e1.j1 d; + + public synthetic b0(e1.j1 p1, int p2) + { + this.c = p2; + this.d = p1; + return; + } + + public final Object invoke(Object p6) + { + switch (this.c) { + case 0: + kotlin.jvm.internal.s.f(((String) p6), "new"); + if (((String) p6).length() <= 11) { + e1.j1 v0_52 = new StringBuilder(); + int v1_4 = ((String) p6).length(); + int v2_2 = 0; + while (v2_2 < v1_4) { + char v3_2 = ((String) p6).charAt(v2_2); + if (Character.isDigit(v3_2)) { + v0_52.append(v3_2); + } + v2_2++; + } + this.d.setValue(v0_52.toString()); + } + return p6.a0.a; + case 1: + kotlin.jvm.internal.s.f(((String) p6), "new"); + String v6_44 = ((String) p6).toUpperCase(java.util.Locale.ROOT); + kotlin.jvm.internal.s.e(v6_44, "toUpperCase(...)"); + e1.j1 v0_45 = new StringBuilder(); + int v1_2 = v6_44.length(); + int v2_1 = 0; + while (v2_1 < v1_2) { + char v3_1 = v6_44.charAt(v2_1); + if (((48 <= v3_1) && (v3_1 < 58)) || ((65 <= v3_1) && (v3_1 < 71))) { + v0_45.append(v3_1); + } + v2_1++; + } + this.d.setValue(v0_45.toString()); + return p6.a0.a; + case 2: + kotlin.jvm.internal.s.f(((String) p6), "error"); + this.d.setValue(new a9.x2(((String) p6))); + return p6.a0.a; + case 3: + kotlin.jvm.internal.s.f(((String) p6), "it"); + this.d.setValue(((String) p6)); + return p6.a0.a; + case 4: + kotlin.jvm.internal.s.f(((String) p6), "it"); + this.d.setValue(((String) p6)); + return p6.a0.a; + case 5: + ((Boolean) p6).booleanValue(); + this.d.setValue(((Boolean) p6)); + return p6.a0.a; + case 6: + ((Integer) p6).intValue(); + this.d.setValue(((Integer) p6)); + return p6.a0.a; + case 7: + kotlin.jvm.internal.s.f(((String) p6), "it"); + this.d.setValue(((String) p6)); + return p6.a0.a; + case 8: + kotlin.jvm.internal.s.f(((String) p6), "it"); + this.d.setValue(((String) p6)); + return p6.a0.a; + case 9: + kotlin.jvm.internal.s.f(((String) p6), "v"); + e1.j1 v0_20 = new StringBuilder(); + int v1_1 = ((String) p6).length(); + int v2_0 = 0; + while (v2_0 < v1_1) { + char v3_0 = ((String) p6).charAt(v2_0); + if (Character.isLetterOrDigit(v3_0)) { + v0_20.append(v3_0); + } + v2_0++; + } + String v6_22 = v0_20.toString().toUpperCase(java.util.Locale.ROOT); + kotlin.jvm.internal.s.e(v6_22, "toUpperCase(...)"); + this.d.setValue(i7.i.Q0(12, v6_22)); + return p6.a0.a; + case 10: + kotlin.jvm.internal.s.f(((qbsg.top.ble.data.BleDeviceInfo) p6), "it"); + return Boolean.valueOf((((java.util.Map) this.d.getValue()).keySet().contains(((qbsg.top.ble.data.BleDeviceInfo) p6).getAddress()) ^ 1)); + case 11: + int v1_0; + kotlin.jvm.internal.s.f(((qbsg.top.ble.data.BleDeviceInfo) p6), "it"); + if (!((Boolean) this.d.getValue()).booleanValue()) { + v1_0 = 1; + } else { + v1_0 = 0; + if (i7.p.l0(((qbsg.top.ble.data.BleDeviceInfo) p6).getName(), "NRF-", 0)) { + } + } + return Boolean.valueOf(v1_0); + case 12: + kotlin.jvm.internal.s.f(((String) p6), "it"); + this.d.setValue(((String) p6)); + return p6.a0.a; + case 13: + kotlin.jvm.internal.s.f(((String) p6), "it"); + this.d.setValue(((String) p6)); + return p6.a0.a; + case 14: + ((Boolean) p6).booleanValue(); + this.d.setValue(((Boolean) p6)); + return p6.a0.a; + case 15: + kotlin.jvm.internal.s.f(((v2.h0) p6), "$this$LayerBackdrop"); + ((a7.c) this.d.getValue()).invoke(((v2.h0) p6)); + return p6.a0.a; + case 16: + ((a7.c) this.d.getValue()).invoke(((a2.b) p6)); + return p6.a0.a; + case 17: + this.d.setValue(((t2.u) p6)); + return p6.a0.a; + case 18: + this.d.setValue(((t2.u) p6)); + return p6.a0.a; + case 19: + this.d.setValue(((t2.u) p6)); + return p6.a0.a; + case 20: + kotlin.jvm.internal.s.f(((String) p6), "it"); + this.d.setValue(((String) p6)); + return p6.a0.a; + case 21: + kotlin.jvm.internal.s.f(((String) p6), "it"); + this.d.setValue(((String) p6)); + return p6.a0.a; + case 22: + kotlin.jvm.internal.s.f(((o8.f4) p6), "it"); + this.d.setValue(((o8.f4) p6)); + return p6.a0.a; + case 23: + kotlin.jvm.internal.s.f(((o8.c4) p6), "it"); + this.d.setValue(((o8.c4) p6)); + return p6.a0.a; + case 24: + kotlin.jvm.internal.s.f(((String) p6), "it"); + this.d.setValue(((String) p6)); + return p6.a0.a; + case 25: + kotlin.jvm.internal.s.f(((t2.u) p6), "coords"); + this.d.setValue(new a2.b(((t2.u) p6).r0(0))); + return p6.a0.a; + case 26: + kotlin.jvm.internal.s.f(((java.util.Map) p6), "bounds"); + this.d.setValue(((java.util.Map) p6)); + return p6.a0.a; + case 27: + ((Boolean) p6).booleanValue(); + this.d.setValue(((Boolean) p6)); + return p6.a0.a; + case 28: + this.d.setValue(new s3.l(((s3.l) p6).a)); + return p6.a0.a; + default: + kotlin.jvm.internal.s.f(((String) p6), "it"); + this.d.setValue(((String) p6)); + return p6.a0.a; + } + } +} diff --git a/analysis/apk/decomp/a9_v2.java b/analysis/apk/decomp/a9_v2.java new file mode 100644 index 0000000..caa1d23 --- /dev/null +++ b/analysis/apk/decomp/a9_v2.java @@ -0,0 +1,281 @@ +package a9; +public abstract class v2 { + + public static final void a(z7.d0 p34, qbsg.top.ble.data.BleException p35, a7.a p36, a7.a p37, a7.c p38, a7.c p39, a7.a p40, a7.a p41, a7.c p42, a7.a p43, a7.a p44, a7.a p45, a7.c p46, z7.e0 p47, a7.a p48, a7.a p49, a7.c p50, e1.n0 p51, int p52) + { + z7.j0 v10_33; + kotlin.jvm.internal.s.f(p34, "setup"); + a7.c v16_0 = p34.c; + o1.f v17_0 = p34.b; + kotlin.jvm.internal.s.f(p36, "onScan"); + kotlin.jvm.internal.s.f(p37, "onFilterToggle"); + kotlin.jvm.internal.s.f(p38, "onToggleSelect"); + kotlin.jvm.internal.s.f(p39, "onSelectAll"); + kotlin.jvm.internal.s.f(p40, "onClearAll"); + kotlin.jvm.internal.s.f(p41, "onPickFirmware"); + kotlin.jvm.internal.s.f(p42, "onCommandsChange"); + kotlin.jvm.internal.s.f(p43, "onRunOta"); + kotlin.jvm.internal.s.f(p44, "onRunCmd"); + kotlin.jvm.internal.s.f(p45, "onRunActivation"); + kotlin.jvm.internal.s.f(p46, "onDeselectAddresses"); + kotlin.jvm.internal.s.f(p48, "onBack"); + kotlin.jvm.internal.s.f(p49, "onResolveError"); + kotlin.jvm.internal.s.f(p50, "onMaxConcurrentChange"); + p51.b0(1395853593); + int v18_0 = 4; + if (!p51.i(p34)) { + v10_33 = 2; + } else { + v10_33 = 4; + } + e1.a3 v20_12; + a9.s v21_0 = 16; + if (!p51.i(p35)) { + v20_12 = 16; + } else { + v20_12 = 32; + } + e1.a3 v20_14; + int v23_0 = 128; + if (!p51.i(p36)) { + v20_14 = 128; + } else { + v20_14 = 256; + } + e1.a3 v20_16; + int v25 = 1024; + if (!p51.i(p37)) { + v20_16 = 1024; + } else { + v20_16 = 2048; + } + e1.a3 v20_18; + int v27 = 8192; + if (!p51.i(p38)) { + v20_18 = 8192; + } else { + v20_18 = 16384; + } + e1.a3 v20_20; + int v29 = 65536; + if (!p51.i(p39)) { + v20_20 = 65536; + } else { + v20_20 = 131072; + } + e1.a3 v20_1; + int v31 = 524288; + if (!p51.i(p40)) { + v20_1 = 524288; + } else { + v20_1 = 1048576; + } + e1.a3 v20_3; + if (!p51.i(p41)) { + v20_3 = 4194304; + } else { + v20_3 = 8388608; + } + e1.a3 v20_5; + if (!p51.i(p42)) { + v20_5 = 33554432; + } else { + v20_5 = 67108864; + } + e1.a3 v20_7; + if (!p51.i(p43)) { + v20_7 = 268435456; + } else { + v20_7 = 536870912; + } + z7.j0 v10_4 = ((((((((((p52 | v10_33) | v20_12) | v20_14) | v20_16) | v20_18) | v20_20) | v20_1) | v20_3) | v20_5) | v20_7); + if (!p51.i(p44)) { + v18_0 = 2; + } + if (p51.i(p45)) { + v21_0 = 32; + } + if (p51.i(p46)) { + v23_0 = 256; + } + if (p51.i(p47)) { + v25 = 2048; + } + if (p51.i(p48)) { + v27 = 16384; + } + if (p51.i(p49)) { + v29 = 131072; + } + if (p51.i(p50)) { + v31 = 1048576; + } + if (((v10_4 & 306783379) == 306783378) && ((((((((v18_0 | v21_0) | v23_0) | v25) | v27) | v29) | v31) & 599187) == 599186)) { + String v0_5 = 0; + } else { + v0_5 = 1; + } + if (!p51.R((v10_4 & 1), v0_5)) { + p51.U(); + } else { + String v0_8 = y.w.a(0, 3, p51); + o1.f v1_4 = p51.O(); + z7.j0 v10_7 = e1.n.a; + if (v1_4 == v10_7) { + v1_4 = e1.h0.g(kotlin.coroutines.EmptyCoroutineContext.INSTANCE, p51); + p51.k0(v1_4); + } + int v18_8 = ((kotlinx.coroutines.CoroutineScope) v1_4); + o1.f v1_6 = p51.O(); + if (v1_6 == v10_7) { + v1_6 = e1.t.h(new a9.k2(v0_8, 0)); + p51.k0(v1_6); + } + e1.a3 v20_10 = ((e1.a3) v1_6); + o1.f v1_9 = p51.O(); + if (v1_9 == v10_7) { + v1_9 = e1.t.n(Boolean.FALSE); + p51.k0(v1_9); + } + o1.f v1_11 = ((e1.j1) v1_9); + int v2_3 = p51.O(); + if (v2_3 == v10_7) { + v2_3 = e1.t.n(""); + p51.k0(v2_3); + } + int v2_5 = ((e1.j1) v2_3); + a9.s v21_1 = v0_8; + String v0_9 = p51.O(); + if (v0_9 == v10_7) { + v0_9 = new z1.v(); + p51.k0(v0_9); + } + String v0_11 = ((z1.v) v0_9); + String v3_2 = ((Boolean) v1_11.getValue()); + v3_2.getClass(); + java.util.ArrayList v4_1 = p51.O(); + if (v4_1 == v10_7) { + v4_1 = new a9.q2(v0_11, v1_11, 0, 0); + p51.k0(v4_1); + } + e1.h0.c(((a7.e) v4_1), p51, v3_2); + String v3_5 = ((Boolean) v1_11.getValue()).booleanValue(); + java.util.ArrayList v4_4 = p51.O(); + if (v4_4 == v10_7) { + v4_4 = new a9.u0(6, v1_11, v2_5); + p51.k0(v4_4); + } + int v22_1; + o1.f v17_1; + a7.c v16_2; + q8.k.e(v3_5, ((a7.a) v4_4), p51, 48); + java.util.ArrayList v4_7 = v17_0; + Object v6_1 = v16_0; + String v3_9 = (p51.g(((String) v2_5.getValue())) | (p51.g(v4_7) | p51.h(v6_1))); + int v5_6 = p51.O(); + if ((v3_9 == null) && (v5_6 != v10_7)) { + v16_2 = v0_11; + v17_1 = v1_11; + v22_1 = v2_5; + } else { + z7.j0 v10_13; + if (v6_1 == null) { + v10_13 = v4_7; + } else { + String v3_11 = new java.util.ArrayList(); + java.util.ArrayList v4_8 = v4_7.iterator(); + while (v4_8.hasNext()) { + int v5_14 = v4_8.next(); + a7.c v16_6 = v0_11; + if (i7.p.l0(((z7.j0) v5_14).b, "NRF-", 0)) { + v3_11.add(v5_14); + } + v0_11 = v16_6; + } + v10_13 = v3_11; + } + v16_2 = v0_11; + if (i7.i.x0(((String) v2_5.getValue()))) { + v5_6 = v10_13; + } else { + String v0_19 = i7.i.S0(((String) v2_5.getValue())).toString().toLowerCase(java.util.Locale.ROOT); + kotlin.jvm.internal.s.e(v0_19, "toLowerCase(...)"); + java.util.ArrayList v4_10 = new java.util.ArrayList(); + int v5_8 = v10_13.iterator(); + while (v5_8.hasNext()) { + Object v6_9 = v5_8.next(); + z7.j0 v10_26 = ((z7.j0) v6_9); + o1.f v17_4 = v1_11; + int v22_2 = v2_5; + int v2_14 = java.util.Locale.ROOT; + o1.f v1_25 = v10_26.b.toLowerCase(v2_14); + kotlin.jvm.internal.s.e(v1_25, "toLowerCase(...)"); + int v23_1 = v5_8; + if (i7.i.o0(v1_25, v0_19, 0)) { + v4_10.add(v6_9); + } else { + o1.f v1_28 = v10_26.a.toLowerCase(v2_14); + kotlin.jvm.internal.s.e(v1_28, "toLowerCase(...)"); + if (i7.i.o0(v1_28, v0_19, 0)) { + } + } + v1_11 = v17_4; + v2_5 = v22_2; + v5_8 = v23_1; + } + v5_6 = v4_10; + } + v17_1 = v1_11; + v22_1 = v2_5; + p51.k0(v5_6); + } + String v0_21 = ((java.util.List) v5_6); + Object v6_4 = new java.util.ArrayList(q6.p.c0(v0_21, 10)); + o1.f v1_14 = v0_21.iterator(); + while (v1_14.hasNext()) { + v6_4.add(((z7.j0) v1_14.next()).a); + } + y0.q9.a(0, o1.l.d(-883814435, new a9.l2(v17_1, v16_2, v22_1, p34, p48, v6_4, p40, p39, p37, p36), p51), 0, 0, 0, 0, 0, 0, 0, o1.l.d(-280072152, new a9.m2(p35, p49, v0_21, p34, v21_1, p38, v20_10, v18_8, p47, p41, p42, p43, p44, p45, p46, p50, 0), p51), p51, 805306416, 509); + } + String v0_27 = p51.t(); + if (v0_27 != null) { + v0_27.d = new a9.s(p34, p35, p36, p37, p38, p39, p40, p41, p42, p43, p44, p45, p46, p47, p48, p49, p50, p52); + } + return; + } + + public static final void b(int p17, e1.n0 p18, boolean p19) + { + a9.l1 v1_12; + p18.b0(-745178988); + if (!p18.h(p19)) { + v1_12 = 2; + } else { + v1_12 = 4; + } + int v2_0; + a9.l1 v1_0 = (p17 | v1_12); + if ((v1_0 & 3) == 2) { + v2_0 = 0; + } else { + v2_0 = 1; + } + if (!p18.R((v1_0 & 1), v2_0)) { + p18.U(); + } else { + if (!p19) { + p18.a0(1058956622); + p18.q(0); + } else { + p18.a0(1058793252); + y0.fe.c(w.y1.d(w.y1.c(u1.n.a, 1065353216), ((float) 8)), ((y0.e6) p18.k(y0.g6.a)).a.a, 0, 0, 0, 0, 0, 0, 0, p18, 6, 508); + p18.q(0); + } + } + e1.d2 v0_2 = p18.t(); + if (v0_2 != null) { + v0_2.d = new a9.l1(p17, p19); + } + return; + } +} diff --git a/analysis/apk/decomp/c9_m.java b/analysis/apk/decomp/c9_m.java new file mode 100644 index 0000000..553abdd --- /dev/null +++ b/analysis/apk/decomp/c9_m.java @@ -0,0 +1,308 @@ +package c9; +public final synthetic class m implements a7.g { + public final synthetic qbsg.top.ble.data.BleException c; + public final synthetic a7.a d; + public final synthetic y.t e; + public final synthetic java.util.List f; + public final synthetic java.util.Map g; + public final synthetic qbsg.top.ble.data.BleDeviceInfo h; + public final synthetic a7.c i; + public final synthetic a7.c j; + public final synthetic a7.c k; + public final synthetic boolean l; + + public synthetic m(qbsg.top.ble.data.BleException p1, a7.a p2, y.t p3, java.util.List p4, java.util.Map p5, qbsg.top.ble.data.BleDeviceInfo p6, a7.c p7, a7.c p8, a7.c p9, boolean p10) + { + this.c = p1; + this.d = p2; + this.e = p3; + this.f = p4; + this.g = p5; + this.h = p6; + this.i = p7; + this.j = p8; + this.k = p9; + this.l = p10; + return; + } + + public final Object invoke(Object p45, Object p46, Object p47, Object p48) + { + ((Integer) p48).getClass(); + kotlin.jvm.internal.s.f(((m.p) p45), "$this$AnimatedContent"); + kotlin.jvm.internal.s.f(((c9.r0) p46), "state"); + Object[] v2_0 = u1.n.a; + String v3_2 = this.c; + long v7_2 = this.d; + int v4_4 = u1.b.e; + g3.p0 v6_2 = e1.n.a; + String v26 = 0; + switch (((c9.r0) p46).ordinal()) { + case 0: + java.util.Iterator v0_30; + java.util.Iterator v0_29 = v3_2; + ((e1.n0) p47).a0(-1342132113); + String v3_22 = u3.a.o(); + int v4_33 = new Object[0]; + int v4_34 = h8.a.a("\u7f3a\u5c11\u5fc5\u8981\u6743\u9650", v4_33, ((e1.n0) p47)); + if (!(v0_29 instanceof qbsg.top.ble.data.BleException$PermissionDenied)) { + v0_30 = 0; + } else { + v0_30 = ((qbsg.top.ble.data.BleException$PermissionDenied) v0_29); + } + int v9_16; + if (v0_30 == null) { + v9_16 = 0; + } else { + v9_16 = v0_30.getMissingPermissions(); + } + java.util.Iterator v0_35; + int v1_43; + if (v9_16 != 0) { + ((e1.n0) p47).a0(-1342124202); + java.util.Iterator v0_33 = ((e1.n0) p47).O(); + if (v0_33 == v6_2) { + v0_33 = new a9.b(24); + ((e1.n0) p47).k0(v0_33); + } + v0_35 = q6.o.s0(v9_16, "\n", 0, 0, ((a7.c) v0_33), 30); + v1_43 = 0; + ((e1.n0) p47).q(0); + } else { + ((e1.n0) p47).a0(1343822699); + ((e1.n0) p47).q(0); + v1_43 = 0; + v0_35 = 0; + } + if (v0_35 != null) { + ((e1.n0) p47).a0(1343897782); + v26 = h8.a.a("\u9700\u8981\u4ee5\u4e0b\u6743\u9650\u624d\u80fd\u626b\u63cf\u8bbe\u5907\uff1a\n%s", new Object[] {v0_35}), ((e1.n0) p47)); + } else { + ((e1.n0) p47).a0(1343897781); + } + ((e1.n0) p47).q(v1_43); + if (v26 == null) { + v26 = ""; + } + Object[] v2_13 = new Object[v1_43]; + c9.a.d(v3_22, v4_34, v26, h8.a.a("\u53bb\u6388\u6743", v2_13, ((e1.n0) p47)), v7_2, ((e1.n0) p47), 0); + ((e1.n0) p47).q(v1_43); + break; + case 1: + ((e1.n0) p47).a0(-1342113783); + Object[] v2_6 = new Object[0]; + Object[] v2_7 = new Object[0]; + Object[] v2_8 = new Object[0]; + c9.a.d(u3.a.c(), h8.a.a("\u84dd\u7259\u672a\u5f00\u542f", v2_6, ((e1.n0) p47)), h8.a.a("\u8bf7\u5f00\u542f\u84dd\u7259\u540e\u626b\u63cf\u9644\u8fd1\u7684\u8bbe\u5907", v2_7, ((e1.n0) p47)), h8.a.a("\u5f00\u542f\u84dd\u7259", v2_8, ((e1.n0) p47)), v7_2, ((e1.n0) p47), 0); + ((e1.n0) p47).q(0); + break; + case 2: + ((e1.n0) p47).a0(-1342102628); + String v3_20 = u3.a.o(); + int v1_35 = new Object[0]; + int v4_30 = h8.a.a("\u5b9a\u4f4d\u672a\u5f00\u542f", v1_35, ((e1.n0) p47)); + int v1_36 = new Object[0]; + long v5_37 = h8.a.a("Android \u7cfb\u7edf\u8981\u6c42\u5f00\u542f\u5b9a\u4f4d\u6743\u9650\u540e\n\u624d\u80fd\u626b\u63cf BLE \u8bbe\u5907", v1_36, ((e1.n0) p47)); + java.util.Iterator v0_23 = ((e1.n0) p47).O(); + if (v0_23 == v6_2) { + v0_23 = new a5.a(14); + ((e1.n0) p47).k0(v0_23); + } + c9.a.d(v3_20, v4_30, v5_37, 0, ((a7.a) v0_23), ((e1.n0) p47), 27648); + ((e1.n0) p47).q(0); + break; + case 3: + ((e1.n0) p47).a0(-1342092266); + java.util.Iterator v0_14 = v3_2; + String v3_19 = u3.a.o(); + Object[] v2_5 = new Object[0]; + int v4_28 = h8.a.a("\u626b\u63cf\u5931\u8d25", v2_5, ((e1.n0) p47)); + if (v0_14 != null) { + v26 = v0_14.getMessage(); + } + if (v26 != null) { + ((e1.n0) p47).a0(-1342087958); + } else { + ((e1.n0) p47).a0(-1342087245); + int v1_33 = new Object[0]; + v26 = h8.a.a("\u672a\u77e5\u9519\u8bef", v1_33, ((e1.n0) p47)); + } + ((e1.n0) p47).q(0); + long v5_34 = v26; + java.util.Iterator v0_18 = ((e1.n0) p47).O(); + if (v0_18 == v6_2) { + v0_18 = new a5.a(14); + ((e1.n0) p47).k0(v0_18); + } + c9.a.d(v3_19, v4_28, v5_34, 0, ((a7.a) v0_18), ((e1.n0) p47), 27648); + ((e1.n0) p47).q(0); + break; + case 4: + ((e1.n0) p47).a0(1340587075); + int v1_27 = w.o.d(v4_4, 0); + String v3_16 = Long.hashCode(((e1.n0) p47).T); + int v4_23 = ((e1.n0) p47).m(); + java.util.Iterator v0_10 = u1.a.c(((e1.n0) p47), w.y1.c); + v2.h.b.getClass(); + ((e1.n0) p47).d0(); + if (!((e1.n0) p47).S) { + ((e1.n0) p47).n0(); + } else { + ((e1.n0) p47).l(v2.g.b); + } + e1.t.v(v2.g.f, ((e1.n0) p47), v1_27); + e1.t.v(v2.g.e, ((e1.n0) p47), v4_23); + e1.t.v(v2.g.g, ((e1.n0) p47), Integer.valueOf(v3_16)); + e1.t.r(v2.g.h, ((e1.n0) p47)); + e1.t.v(v2.g.d, ((e1.n0) p47), v0_10); + y0.o1.e(w.y1.m(v2_0, ((float) 60)), 0, 0, 0, 0, ((e1.n0) p47), 6); + ((e1.n0) p47).q(1); + ((e1.n0) p47).q(0); + break; + case 5: + ((e1.n0) p47).a0(1340978481); + String v3_25 = w.o.d(v4_4, 0); + int v4_37 = Long.hashCode(((e1.n0) p47).T); + g3.p0 v6_0 = ((e1.n0) p47).m(); + int v1_1 = u1.a.c(((e1.n0) p47), w.y1.c); + v2.h.b.getClass(); + long v7_1 = v2.g.b; + ((e1.n0) p47).d0(); + if (!((e1.n0) p47).S) { + ((e1.n0) p47).n0(); + } else { + ((e1.n0) p47).l(v7_1); + } + int v11_1 = v2.g.f; + e1.t.v(v11_1, ((e1.n0) p47), v3_25); + String v3_1 = v2.g.e; + e1.t.v(v3_1, ((e1.n0) p47), v6_0); + g3.p0 v6_1 = v2.g.g; + e1.t.v(v6_1, ((e1.n0) p47), Integer.valueOf(v4_37)); + int v4_1 = v2.g.h; + e1.t.r(v4_1, ((e1.n0) p47)); + long v12_0 = v2.g.d; + e1.t.v(v12_0, ((e1.n0) p47), v1_1); + int v1_5 = w.c.B(v2_0, ((float) 32), 0, 2); + long v5_1 = w.t.a(w.i.c, u1.b.n, ((e1.n0) p47), 48); + int v9_1 = Long.hashCode(((e1.n0) p47).T); + int v13_2 = ((e1.n0) p47).m(); + int v1_6 = u1.a.c(((e1.n0) p47), v1_5); + ((e1.n0) p47).d0(); + if (!((e1.n0) p47).S) { + ((e1.n0) p47).n0(); + } else { + ((e1.n0) p47).l(v7_1); + } + int v4_9; + long v5_5; + e1.t.v(v11_1, ((e1.n0) p47), v5_1); + e1.t.v(v3_1, ((e1.n0) p47), v13_2); + p.p1.v(v9_1, ((e1.n0) p47), v6_1, ((e1.n0) p47), v4_1); + e1.t.v(v12_0, ((e1.n0) p47), v1_6); + int v9_2 = new Object[0]; + int v9_3 = y0.g6.a; + u1.q v38 = v6_1; + v2.e v37 = v3_1; + a7.c v36 = v11_1; + y.t v39 = v4_1; + int v35 = v7_1; + e1.b3 v41 = v9_3; + e1.n0 v40 = v12_0; + y0.bd.b(h8.a.a("\u7a7a\u5217\u8868", v9_2, ((e1.n0) p47)), 0, ((y0.e6) ((e1.n0) p47).k(v9_3)).a.q, 0, 0, k3.y.j, 0, 0, new r3.k(3), 0, 0, 0, 0, 0, ((y0.e6) ((e1.n0) p47).k(v9_3)).b.e, ((e1.n0) p47), 1572864, 0, 129978); + e1.n0 v8_0 = ((e1.n0) p47); + w.c.h(((e1.n0) p47), w.c.B(v2_0, 0, ((float) 6), 1)); + if (!this.l) { + v5_5 = 0; + ((e1.n0) p47).a0(1440287316); + g3.p0 v6_3 = new Object[0]; + v4_9 = h8.a.a("\u4efb\u4f55", v6_3, ((e1.n0) p47)); + } else { + ((e1.n0) p47).a0(1440286585); + v5_5 = 0; + g3.p0 v6_4 = new Object[0]; + v4_9 = h8.a.a("NRF-\u5f00\u5934\u7684", v6_4, ((e1.n0) p47)); + } + ((e1.n0) p47).q(v5_5); + long v5_7 = v41; + y0.bd.b(h8.a.a("\u6ca1\u6709\u626b\u63cf\u5230%s\u84dd\u7259\u8bbe\u5907\n\u8bf7\u6392\u67e5\u4e0b\u9762\u53ef\u80fd\u7684\u539f\u56e0\uff1a", new Object[] {v4_9}), v8_0), 0, ((y0.e6) v8_0.k(v5_7)).a.s, 0, 0, 0, 0, 0, new r3.k(3), 0, 0, 0, 0, 0, ((y0.e6) v8_0.k(v5_7)).b.k, ((e1.n0) p47), 0, 0, 130042); + e1.n0 v8_1 = ((e1.n0) p47); + w.c.h(((e1.n0) p47), w.c.B(v2_0, 0, ((float) 8), 1)); + int v4_16 = new Object[0]; + g3.p0 v6_9 = new Object[0]; + long v7_5 = new Object[0]; + int v9_7 = new Object[0]; + int v1_19 = q6.n.Y(new String[] {h8.a.a("\u8bbe\u5907\u8ddd\u79bb\u624b\u673a\u592a\u8fdc\u4e86\uff1f", v4_16, ((e1.n0) p47)), h8.a.a("\u5df2\u7ecf\u88ab\u5176\u4ed6\u8bbe\u5907\u8fde\u63a5\u4e86\uff1f", v6_9, v8_1), h8.a.a("\u8bbe\u5907\u65ad\u7535\u4e86 / \u5173\u673a\u4e86\uff1f", v7_5, v8_1), h8.a.a("\u8bbe\u5907\u4e0d\u88ab\u652f\u6301\uff1f", v9_7, v8_1)})); + int v4_19 = 1065353216; + long v5_15 = w.y1.c(v2_0, 1065353216); + java.util.Iterator v0_2 = w.t.a(w.i.g(((float) 8)), u1.b.m, ((e1.n0) p47), 6); + String v3_12 = Long.hashCode(((e1.n0) p47).T); + g3.p0 v6_14 = ((e1.n0) p47).m(); + long v5_16 = u1.a.c(((e1.n0) p47), v5_15); + ((e1.n0) p47).d0(); + if (!((e1.n0) p47).S) { + ((e1.n0) p47).n0(); + } else { + ((e1.n0) p47).l(v35); + } + e1.t.v(v36, ((e1.n0) p47), v0_2); + e1.t.v(v37, ((e1.n0) p47), v6_14); + p.p1.v(v3_12, ((e1.n0) p47), v38, ((e1.n0) p47), v39); + e1.t.v(v40, ((e1.n0) p47), v5_16); + ((e1.n0) p47).a0(596955405); + java.util.Iterator v0_7 = v1_19.iterator(); + int v15_7 = 0; + while (v0_7.hasNext()) { + int v1_22 = v0_7.next(); + String v3_13 = (v15_7 + 1); + if (v15_7 < 0) { + k3.t.W(); + throw 0; + } else { + int v1_23 = ((String) v1_22); + long v5_21 = new StringBuilder(); + v5_21.append(v3_13); + v5_21.append(". "); + v5_21.append(v1_23); + long v5_22 = y0.g6.a; + long v7_10 = v4_19; + e1.n0 v22_2 = v8_1; + int v27_1 = v7_10; + int v1_26 = v3_13; + y0.bd.b(v5_21.toString(), w.y1.c(v2_0, v7_10), ((y0.e6) v8_1.k(v5_22)).a.q, 0, 0, k3.y.h, 0, 0, new r3.k(3), 0, 0, 0, 0, 0, ((y0.e6) v8_1.k(v5_22)).b.k, v22_2, 1572912, 0, 129976); + v15_7 = v1_26; + v8_1 = v22_2; + v4_19 = v27_1; + } + } + v8_1.q(0); + v8_1.q(1); + v8_1.q(1); + v8_1.q(1); + v8_1.q(0); + break; + case 6: + ((e1.n0) p47).a0(1345187568); + long v5_18 = new w.p1(((float) 12), ((float) 8), ((float) 12), ((float) 8)); + int v1_24 = w.i.g(((float) 6)); + Object[] v2_3 = this.f; + long v7_12 = this.g; + int v9_11 = this.h; + k3.y v10_6 = this.i; + int v11_8 = this.j; + long v12_5 = this.k; + int v4_35 = (((((((e1.n0) p47).i(v2_3) | ((e1.n0) p47).i(v7_12)) | ((e1.n0) p47).g(v9_11)) | ((e1.n0) p47).g(v10_6)) | ((e1.n0) p47).g(v11_8)) | ((e1.n0) p47).g(v12_5)); + int v13_11 = ((e1.n0) p47).O(); + if ((v4_35 != 0) || (v13_11 == v6_2)) { + v13_11 = new a9.s3(v2_3, v7_12, v9_11, v10_6, v11_8, v12_5, 3); + ((e1.n0) p47).k0(v13_11); + } + v1.p.c(w.y1.c, this.e, v5_18, v1_24, 0, 0, 0, 0, ((a7.c) v13_11), ((e1.n0) p47), 24966, 488); + ((e1.n0) p47).q(0); + break; + default: + throw p.p1.e(-1342224292, ((e1.n0) p47), 0); + } + return p6.a0.a; + } +} diff --git a/analysis/apk/decomp/qbsg_top_ble_BleManager$startDeviceInfoRead$1.java b/analysis/apk/decomp/qbsg_top_ble_BleManager$startDeviceInfoRead$1.java new file mode 100644 index 0000000..d95bd47 --- /dev/null +++ b/analysis/apk/decomp/qbsg_top_ble_BleManager$startDeviceInfoRead$1.java @@ -0,0 +1,83 @@ +package qbsg.top.ble; +final class BleManager$startDeviceInfoRead$1 extends kotlin.coroutines.jvm.internal.SuspendLambda implements a7.e { + final synthetic String $address; + final synthetic android.bluetooth.BluetoothGatt $gatt; + int label; + final synthetic qbsg.top.ble.BleManager this$0; + + public BleManager$startDeviceInfoRead$1(qbsg.top.ble.BleManager p1, String p2, android.bluetooth.BluetoothGatt p3, kotlin.coroutines.Continuation p4) + { + this.this$0 = p1; + this.$address = p2; + this.$gatt = p3; + super(2, p4); + return; + } + + public final kotlin.coroutines.Continuation create(Object p4, kotlin.coroutines.Continuation p5) + { + return new qbsg.top.ble.BleManager$startDeviceInfoRead$1(this.this$0, this.$address, this.$gatt, p5); + } + + public bridge synthetic Object invoke(Object p1, Object p2) + { + return this.invoke(((kotlinx.coroutines.CoroutineScope) p1), ((kotlin.coroutines.Continuation) p2)); + } + + public final Object invoke(kotlinx.coroutines.CoroutineScope p1, kotlin.coroutines.Continuation p2) + { + return ((qbsg.top.ble.BleManager$startDeviceInfoRead$1) this.create(p1, p2)).invokeSuspend(p6.a0.a); + } + + public final Object invokeSuspend(Object p8) + { + int v0_0 = kotlin.coroutines.intrinsics.IntrinsicsKt.getCOROUTINE_SUSPENDED(); + String v1_1 = this.label; + if (v1_1 == null) { + q6.y.Y(p8); + p6.a0 v8_10 = qbsg.top.ble.BleManager.access$getReadChannels$p(this.this$0); + String v1_0 = this.$address; + if (v8_10.get(v1_0) == null) { + v8_10.putIfAbsent(v1_0, kotlinx.coroutines.channels.ChannelKt.Channel$default(2147483647, 0, 0, 6, 0)); + } + p6.a0 v8_1 = this.this$0; + String v1_2 = this.$gatt; + java.util.UUID v4_3 = this.$address; + kotlin.jvm.internal.s.c(v4_3); + String v5_1 = qbsg.top.ble.BleManagerKt.access$getMODEL_NUMBER_UUID$p(); + kotlin.jvm.internal.s.e(v5_1, "access$getMODEL_NUMBER_UUID$p(...)"); + this.label = 1; + if (qbsg.top.ble.BleManager.access$readDeviceInfoString(v8_1, v1_2, v4_3, v5_1, this) != v0_0) { + p6.a0 v8_3 = this.this$0; + String v1_3 = this.$gatt; + String v3_0 = this.$address; + kotlin.jvm.internal.s.c(v3_0); + java.util.UUID v4_4 = qbsg.top.ble.BleManagerKt.access$getFIRMWARE_REVISION_UUID$p(); + kotlin.jvm.internal.s.e(v4_4, "access$getFIRMWARE_REVISION_UUID$p(...)"); + this.label = 2; + p8 = qbsg.top.ble.BleManager.access$readDeviceInfoString(v8_3, v1_3, v3_0, v4_4, this); + if (p8 == v0_0) { + return v0_0; + } + } + } else { + if (v1_1 == 1) { + q6.y.Y(p8); + } else { + if (v1_1 != 2) { + throw new IllegalStateException("call to \'resume\' before \'invoke\' with coroutine"); + } else { + q6.y.Y(p8); + } + } + } + p6.a0 v8_6 = ((String) p8); + java.util.concurrent.ConcurrentHashMap vtmp10 = qbsg.top.ble.BleManager.access$getPendingFirmware$p(this.this$0); + if (((String) p8) == null) { + v8_6 = ""; + } + vtmp10.put(this.$address, v8_6); + this.$gatt.requestMtu(512); + return p6.a0.a; + } +} diff --git a/pyproject.toml b/pyproject.toml index a1d4943..0274a08 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,5 +22,5 @@ ppclock = "ppclock.cli:main" where = ["src"] [tool.pytest.ini_options] -pythonpath = ["src"] +pythonpath = ["src", "."] testpaths = ["tests"] diff --git a/tests/test_fw.py b/tests/test_fw.py new file mode 100644 index 0000000..e1e2103 --- /dev/null +++ b/tests/test_fw.py @@ -0,0 +1,64 @@ +"""固件工具测试 — 以原始镜像为黄金样本(app/ 只读)。""" +import zlib +from pathlib import Path + +import pytest + +from tools import fw + +SAMPLE = Path(__file__).parent.parent / "app" / "firmware-PP_da14585_4.2_CH.img" + + +@pytest.fixture +def sample(): + return SAMPLE.read_bytes() + + +class TestParse: + def test_header_fields(self, sample): + info = fw.parse_image(sample) + assert info["magic"] == 0x7051 # 'pQ' + assert info["flags"] == 0x01AA + assert info["body_size"] == len(sample) - 64 + assert info["crc_stored"] == zlib.crc32(sample[64:]) & 0xFFFFFFFF + + def test_crc_valid(self, sample): + assert fw.parse_image(sample)["crc_valid"] is True + + def test_vector_table(self, sample): + info = fw.parse_image(sample) + assert info["initial_sp"] == 0x07FD2B58 + assert info["reset_handler"] == 0x07FC0525 + assert info["load_base"] == 0x07FC0000 + + def test_bad_magic_rejected(self): + with pytest.raises(ValueError): + fw.parse_image(b"\x00" * 128) + + def test_bad_crc_reported(self, sample): + corrupted = bytearray(sample) + corrupted[100] ^= 0xFF + assert fw.parse_image(bytes(corrupted))["crc_valid"] is False + + +class TestPack: + def test_repack_roundtrip_byte_identical(self, sample): + """解包→重打包 == 原文件(字节级)""" + body = fw.unpack_body(sample) + assert fw.pack_image(body, flags=0x01AA) == sample + + def test_pack_computes_crc(self): + body = b"\x58\x2b\xfd\x07" + b"\x00" * 100 + img = fw.pack_image(body) + info = fw.parse_image(img) + assert info["crc_valid"] is True + assert info["body_size"] == len(body) + + def test_patch_and_repack(self, sample): + """修改体→重打包→CRC 更新且可解析""" + body = bytearray(fw.unpack_body(sample)) + body[0x1000] ^= 0xAA + img = fw.pack_image(bytes(body)) + info = fw.parse_image(img) + assert info["crc_valid"] is True + assert img != sample diff --git a/tools/__init__.py b/tools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tools/fw.py b/tools/fw.py new file mode 100644 index 0000000..095c0ee --- /dev/null +++ b/tools/fw.py @@ -0,0 +1,54 @@ +"""DA14585 SUOTA 镜像解析/重打包(tools 交付物)。 +格式(证据:app/ 样本 + CRC 反推验证): + [0:2] magic 'pQ' = 0x7051(LE) + [2:4] flags = 0x01AA + [4:8] body 长度(= 文件长 - 64) + [8:12] CRC32(body)(zlib 多项式,LE) + [12:64] 0xFF 填充 + [64:] 镜像体,起始为 Cortex-M 向量表(SP, Reset, ...),加载基址 0x07FC0000 +""" +from __future__ import annotations + +import struct +import zlib + +MAGIC_BYTES = b"pQ" # 文件头两字节 70 51 +MAGIC = 0x7051 +FLAGS_DEFAULT = 0x01AA +HEADER_SIZE = 64 +LOAD_BASE = 0x07FC0000 + + +def parse_image(data: bytes) -> dict: + if len(data) < HEADER_SIZE: + raise ValueError("文件小于 64 字节头") + if data[0:2] != MAGIC_BYTES: + raise ValueError(f"magic 错误: {data[0:2].hex()}(期望 {MAGIC_BYTES.hex()}='pQ')") + flags, body_size, crc = struct.unpack_from(" bytes: + info = parse_image(data) + return data[HEADER_SIZE:HEADER_SIZE + info["body_size"]] + + +def pack_image(body: bytes, flags: int = FLAGS_DEFAULT) -> bytes: + header = MAGIC_BYTES + struct.pack(" 3 else 0x01AA + Path(sys.argv[2]).write_bytes(pack_image(body, flags)) + print(f"OK: {sys.argv[2]} ({len(body)}B body)")