capacitor-dex-editor 0.0.10 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -424,6 +424,23 @@ public class DexEditorPluginPlugin extends Plugin {
|
|
|
424
424
|
));
|
|
425
425
|
break;
|
|
426
426
|
|
|
427
|
+
case "getClassSmali":
|
|
428
|
+
result.put("data", dexManager.getClassSmaliFromApk(
|
|
429
|
+
params.getString("apkPath"),
|
|
430
|
+
params.getString("dexPath"),
|
|
431
|
+
params.getString("className")
|
|
432
|
+
));
|
|
433
|
+
break;
|
|
434
|
+
|
|
435
|
+
case "saveClassSmali":
|
|
436
|
+
result.put("data", dexManager.saveClassSmaliToApk(
|
|
437
|
+
params.getString("apkPath"),
|
|
438
|
+
params.getString("dexPath"),
|
|
439
|
+
params.getString("className"),
|
|
440
|
+
params.getString("smaliContent")
|
|
441
|
+
));
|
|
442
|
+
break;
|
|
443
|
+
|
|
427
444
|
default:
|
|
428
445
|
result.put("success", false);
|
|
429
446
|
result.put("error", "Unknown action: " + action);
|
|
@@ -1510,4 +1510,359 @@ public class DexManager {
|
|
|
1510
1510
|
}
|
|
1511
1511
|
return className.replace("/", ".");
|
|
1512
1512
|
}
|
|
1513
|
+
|
|
1514
|
+
/**
|
|
1515
|
+
* 将 Java 类名格式转换为 DEX 类型格式
|
|
1516
|
+
* 例如: com.example.Class -> Lcom/example/Class;
|
|
1517
|
+
*/
|
|
1518
|
+
private String convertClassNameToType(String className) {
|
|
1519
|
+
if (className == null) return "";
|
|
1520
|
+
return "L" + className.replace(".", "/") + ";";
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
/**
|
|
1524
|
+
* 从 APK 中的 DEX 文件获取类的 Smali 代码
|
|
1525
|
+
*/
|
|
1526
|
+
public JSObject getClassSmaliFromApk(String apkPath, String dexPath, String className) throws Exception {
|
|
1527
|
+
JSObject result = new JSObject();
|
|
1528
|
+
StringBuilder smali = new StringBuilder();
|
|
1529
|
+
|
|
1530
|
+
Log.d(TAG, "getClassSmaliFromApk: apkPath=" + apkPath + ", dexPath=" + dexPath + ", className=" + className);
|
|
1531
|
+
|
|
1532
|
+
if (className == null || className.isEmpty()) {
|
|
1533
|
+
result.put("smali", "# 未指定类名");
|
|
1534
|
+
return result;
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
String targetType = convertClassNameToType(className);
|
|
1538
|
+
|
|
1539
|
+
java.util.zip.ZipFile zipFile = null;
|
|
1540
|
+
java.io.InputStream dexInputStream = null;
|
|
1541
|
+
|
|
1542
|
+
try {
|
|
1543
|
+
zipFile = new java.util.zip.ZipFile(apkPath);
|
|
1544
|
+
|
|
1545
|
+
// 尝试多种可能的 dexPath 格式
|
|
1546
|
+
java.util.zip.ZipEntry dexEntry = zipFile.getEntry(dexPath);
|
|
1547
|
+
if (dexEntry == null) {
|
|
1548
|
+
dexEntry = zipFile.getEntry(dexPath.replaceFirst("^/+", ""));
|
|
1549
|
+
}
|
|
1550
|
+
if (dexEntry == null) {
|
|
1551
|
+
String fileName = dexPath;
|
|
1552
|
+
if (dexPath.contains("/")) {
|
|
1553
|
+
fileName = dexPath.substring(dexPath.lastIndexOf("/") + 1);
|
|
1554
|
+
}
|
|
1555
|
+
dexEntry = zipFile.getEntry(fileName);
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
if (dexEntry == null) {
|
|
1559
|
+
result.put("smali", "# DEX 文件未找到: " + dexPath);
|
|
1560
|
+
return result;
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
dexInputStream = zipFile.getInputStream(dexEntry);
|
|
1564
|
+
|
|
1565
|
+
// 读取 DEX 文件到内存
|
|
1566
|
+
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
|
1567
|
+
byte[] buffer = new byte[8192];
|
|
1568
|
+
int len;
|
|
1569
|
+
while ((len = dexInputStream.read(buffer)) != -1) {
|
|
1570
|
+
baos.write(buffer, 0, len);
|
|
1571
|
+
}
|
|
1572
|
+
byte[] dexBytes = baos.toByteArray();
|
|
1573
|
+
|
|
1574
|
+
// 解析 DEX 文件
|
|
1575
|
+
DexBackedDexFile dexFile = new DexBackedDexFile(Opcodes.getDefault(), dexBytes);
|
|
1576
|
+
|
|
1577
|
+
// 查找目标类
|
|
1578
|
+
ClassDef targetClass = null;
|
|
1579
|
+
for (ClassDef classDef : dexFile.getClasses()) {
|
|
1580
|
+
if (classDef.getType().equals(targetType)) {
|
|
1581
|
+
targetClass = classDef;
|
|
1582
|
+
break;
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
if (targetClass == null) {
|
|
1587
|
+
result.put("smali", "# 类未找到: " + className + "\n# 目标类型: " + targetType);
|
|
1588
|
+
return result;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
// 生成 Smali 代码
|
|
1592
|
+
smali.append(".class ");
|
|
1593
|
+
// 访问标志
|
|
1594
|
+
int accessFlags = targetClass.getAccessFlags();
|
|
1595
|
+
if ((accessFlags & 0x0001) != 0) smali.append("public ");
|
|
1596
|
+
if ((accessFlags & 0x0010) != 0) smali.append("final ");
|
|
1597
|
+
if ((accessFlags & 0x0020) != 0) smali.append("super ");
|
|
1598
|
+
if ((accessFlags & 0x0200) != 0) smali.append("interface ");
|
|
1599
|
+
if ((accessFlags & 0x0400) != 0) smali.append("abstract ");
|
|
1600
|
+
if ((accessFlags & 0x1000) != 0) smali.append("synthetic ");
|
|
1601
|
+
if ((accessFlags & 0x2000) != 0) smali.append("annotation ");
|
|
1602
|
+
if ((accessFlags & 0x4000) != 0) smali.append("enum ");
|
|
1603
|
+
smali.append(targetClass.getType()).append("\n");
|
|
1604
|
+
|
|
1605
|
+
// 父类
|
|
1606
|
+
String superClass = targetClass.getSuperclass();
|
|
1607
|
+
if (superClass != null) {
|
|
1608
|
+
smali.append(".super ").append(superClass).append("\n");
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
// 源文件
|
|
1612
|
+
String sourceFile = targetClass.getSourceFile();
|
|
1613
|
+
if (sourceFile != null) {
|
|
1614
|
+
smali.append(".source \"").append(sourceFile).append("\"\n");
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
// 实现的接口
|
|
1618
|
+
for (String iface : targetClass.getInterfaces()) {
|
|
1619
|
+
smali.append(".implements ").append(iface).append("\n");
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
smali.append("\n");
|
|
1623
|
+
|
|
1624
|
+
// 字段
|
|
1625
|
+
smali.append("# ========== 字段 ==========\n");
|
|
1626
|
+
for (Field field : targetClass.getFields()) {
|
|
1627
|
+
smali.append(".field ");
|
|
1628
|
+
int fFlags = field.getAccessFlags();
|
|
1629
|
+
if ((fFlags & 0x0001) != 0) smali.append("public ");
|
|
1630
|
+
if ((fFlags & 0x0002) != 0) smali.append("private ");
|
|
1631
|
+
if ((fFlags & 0x0004) != 0) smali.append("protected ");
|
|
1632
|
+
if ((fFlags & 0x0008) != 0) smali.append("static ");
|
|
1633
|
+
if ((fFlags & 0x0010) != 0) smali.append("final ");
|
|
1634
|
+
if ((fFlags & 0x0040) != 0) smali.append("volatile ");
|
|
1635
|
+
if ((fFlags & 0x0080) != 0) smali.append("transient ");
|
|
1636
|
+
if ((fFlags & 0x1000) != 0) smali.append("synthetic ");
|
|
1637
|
+
if ((fFlags & 0x4000) != 0) smali.append("enum ");
|
|
1638
|
+
smali.append(field.getName()).append(":").append(field.getType()).append("\n");
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
smali.append("\n");
|
|
1642
|
+
|
|
1643
|
+
// 方法
|
|
1644
|
+
smali.append("# ========== 方法 ==========\n");
|
|
1645
|
+
for (Method method : targetClass.getMethods()) {
|
|
1646
|
+
smali.append("\n.method ");
|
|
1647
|
+
int mFlags = method.getAccessFlags();
|
|
1648
|
+
if ((mFlags & 0x0001) != 0) smali.append("public ");
|
|
1649
|
+
if ((mFlags & 0x0002) != 0) smali.append("private ");
|
|
1650
|
+
if ((mFlags & 0x0004) != 0) smali.append("protected ");
|
|
1651
|
+
if ((mFlags & 0x0008) != 0) smali.append("static ");
|
|
1652
|
+
if ((mFlags & 0x0010) != 0) smali.append("final ");
|
|
1653
|
+
if ((mFlags & 0x0020) != 0) smali.append("synchronized ");
|
|
1654
|
+
if ((mFlags & 0x0040) != 0) smali.append("bridge ");
|
|
1655
|
+
if ((mFlags & 0x0080) != 0) smali.append("varargs ");
|
|
1656
|
+
if ((mFlags & 0x0100) != 0) smali.append("native ");
|
|
1657
|
+
if ((mFlags & 0x0400) != 0) smali.append("abstract ");
|
|
1658
|
+
if ((mFlags & 0x0800) != 0) smali.append("strictfp ");
|
|
1659
|
+
if ((mFlags & 0x1000) != 0) smali.append("synthetic ");
|
|
1660
|
+
if ((mFlags & 0x10000) != 0) smali.append("constructor ");
|
|
1661
|
+
if ((mFlags & 0x20000) != 0) smali.append("declared-synchronized ");
|
|
1662
|
+
|
|
1663
|
+
smali.append(method.getName());
|
|
1664
|
+
smali.append("(");
|
|
1665
|
+
for (CharSequence param : method.getParameterTypes()) {
|
|
1666
|
+
smali.append(param);
|
|
1667
|
+
}
|
|
1668
|
+
smali.append(")");
|
|
1669
|
+
smali.append(method.getReturnType());
|
|
1670
|
+
smali.append("\n");
|
|
1671
|
+
|
|
1672
|
+
MethodImplementation impl = method.getImplementation();
|
|
1673
|
+
if (impl != null) {
|
|
1674
|
+
smali.append(" .registers ").append(impl.getRegisterCount()).append("\n");
|
|
1675
|
+
|
|
1676
|
+
// 输出指令
|
|
1677
|
+
for (Instruction instruction : impl.getInstructions()) {
|
|
1678
|
+
smali.append(" ").append(instruction.getOpcode().name.toLowerCase());
|
|
1679
|
+
smali.append(" ").append(formatInstruction(instruction));
|
|
1680
|
+
smali.append("\n");
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
smali.append(".end method\n");
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
result.put("smali", smali.toString());
|
|
1688
|
+
|
|
1689
|
+
} finally {
|
|
1690
|
+
if (dexInputStream != null) {
|
|
1691
|
+
try { dexInputStream.close(); } catch (Exception ignored) {}
|
|
1692
|
+
}
|
|
1693
|
+
if (zipFile != null) {
|
|
1694
|
+
try { zipFile.close(); } catch (Exception ignored) {}
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
return result;
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
/**
|
|
1702
|
+
* 格式化指令参数
|
|
1703
|
+
*/
|
|
1704
|
+
private String formatInstruction(Instruction instruction) {
|
|
1705
|
+
StringBuilder sb = new StringBuilder();
|
|
1706
|
+
|
|
1707
|
+
// 处理寄存器指令
|
|
1708
|
+
if (instruction instanceof com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction) {
|
|
1709
|
+
com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction regInstr =
|
|
1710
|
+
(com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction) instruction;
|
|
1711
|
+
sb.append("v").append(regInstr.getRegisterA());
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
if (instruction instanceof com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction) {
|
|
1715
|
+
com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction regInstr =
|
|
1716
|
+
(com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction) instruction;
|
|
1717
|
+
if (sb.length() > 0) sb.append(", ");
|
|
1718
|
+
sb.append("v").append(regInstr.getRegisterB());
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
if (instruction instanceof com.android.tools.smali.dexlib2.iface.instruction.ThreeRegisterInstruction) {
|
|
1722
|
+
com.android.tools.smali.dexlib2.iface.instruction.ThreeRegisterInstruction regInstr =
|
|
1723
|
+
(com.android.tools.smali.dexlib2.iface.instruction.ThreeRegisterInstruction) instruction;
|
|
1724
|
+
if (sb.length() > 0) sb.append(", ");
|
|
1725
|
+
sb.append("v").append(regInstr.getRegisterC());
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
// 处理引用指令
|
|
1729
|
+
if (instruction instanceof com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction) {
|
|
1730
|
+
com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction refInstr =
|
|
1731
|
+
(com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction) instruction;
|
|
1732
|
+
if (sb.length() > 0) sb.append(", ");
|
|
1733
|
+
sb.append(refInstr.getReference().toString());
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
// 处理字面量指令
|
|
1737
|
+
if (instruction instanceof com.android.tools.smali.dexlib2.iface.instruction.WideLiteralInstruction) {
|
|
1738
|
+
com.android.tools.smali.dexlib2.iface.instruction.WideLiteralInstruction litInstr =
|
|
1739
|
+
(com.android.tools.smali.dexlib2.iface.instruction.WideLiteralInstruction) instruction;
|
|
1740
|
+
if (sb.length() > 0) sb.append(", ");
|
|
1741
|
+
sb.append("0x").append(Long.toHexString(litInstr.getWideLiteral()));
|
|
1742
|
+
} else if (instruction instanceof com.android.tools.smali.dexlib2.iface.instruction.NarrowLiteralInstruction) {
|
|
1743
|
+
com.android.tools.smali.dexlib2.iface.instruction.NarrowLiteralInstruction litInstr =
|
|
1744
|
+
(com.android.tools.smali.dexlib2.iface.instruction.NarrowLiteralInstruction) instruction;
|
|
1745
|
+
if (sb.length() > 0) sb.append(", ");
|
|
1746
|
+
sb.append("0x").append(Integer.toHexString(litInstr.getNarrowLiteral()));
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
// 处理偏移指令
|
|
1750
|
+
if (instruction instanceof com.android.tools.smali.dexlib2.iface.instruction.OffsetInstruction) {
|
|
1751
|
+
com.android.tools.smali.dexlib2.iface.instruction.OffsetInstruction offInstr =
|
|
1752
|
+
(com.android.tools.smali.dexlib2.iface.instruction.OffsetInstruction) instruction;
|
|
1753
|
+
if (sb.length() > 0) sb.append(", ");
|
|
1754
|
+
sb.append(":label_").append(offInstr.getCodeOffset());
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
return sb.toString();
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
/**
|
|
1761
|
+
* 保存修改后的 Smali 代码到 APK 中的 DEX 文件
|
|
1762
|
+
* 注意:这是一个复杂操作,需要重新编译 Smali 并修改 DEX 文件
|
|
1763
|
+
*/
|
|
1764
|
+
public JSObject saveClassSmaliToApk(String apkPath, String dexPath, String className, String smaliContent) throws Exception {
|
|
1765
|
+
JSObject result = new JSObject();
|
|
1766
|
+
|
|
1767
|
+
Log.d(TAG, "saveClassSmaliToApk: apkPath=" + apkPath + ", dexPath=" + dexPath + ", className=" + className);
|
|
1768
|
+
Log.d(TAG, "smaliContent length: " + (smaliContent != null ? smaliContent.length() : 0));
|
|
1769
|
+
|
|
1770
|
+
if (className == null || className.isEmpty()) {
|
|
1771
|
+
result.put("success", false);
|
|
1772
|
+
result.put("error", "未指定类名");
|
|
1773
|
+
return result;
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
if (smaliContent == null || smaliContent.isEmpty()) {
|
|
1777
|
+
result.put("success", false);
|
|
1778
|
+
result.put("error", "Smali 内容为空");
|
|
1779
|
+
return result;
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
try {
|
|
1783
|
+
// 创建临时目录存储 smali 文件
|
|
1784
|
+
java.io.File tempDir = new java.io.File(context.getCacheDir(), "smali_temp_" + System.currentTimeMillis());
|
|
1785
|
+
if (!tempDir.mkdirs()) {
|
|
1786
|
+
result.put("success", false);
|
|
1787
|
+
result.put("error", "无法创建临时目录");
|
|
1788
|
+
return result;
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
// 将类名转换为文件路径
|
|
1792
|
+
String classPath = className.replace(".", "/") + ".smali";
|
|
1793
|
+
java.io.File smaliFile = new java.io.File(tempDir, classPath);
|
|
1794
|
+
smaliFile.getParentFile().mkdirs();
|
|
1795
|
+
|
|
1796
|
+
// 写入 smali 文件
|
|
1797
|
+
try (java.io.FileWriter writer = new java.io.FileWriter(smaliFile)) {
|
|
1798
|
+
writer.write(smaliContent);
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
Log.d(TAG, "Smali file written to: " + smaliFile.getAbsolutePath());
|
|
1802
|
+
|
|
1803
|
+
// 使用 smali 库编译 smali 文件为 dex
|
|
1804
|
+
// 注意:这需要 smali 库的支持
|
|
1805
|
+
java.io.File outputDex = new java.io.File(tempDir, "classes_modified.dex");
|
|
1806
|
+
|
|
1807
|
+
// 使用 SmaliOptions 编译
|
|
1808
|
+
com.android.tools.smali.smali.SmaliOptions options = new com.android.tools.smali.smali.SmaliOptions();
|
|
1809
|
+
options.outputDexFile = outputDex.getAbsolutePath();
|
|
1810
|
+
options.apiLevel = 30;
|
|
1811
|
+
|
|
1812
|
+
java.util.List<String> inputFiles = new java.util.ArrayList<>();
|
|
1813
|
+
inputFiles.add(smaliFile.getAbsolutePath());
|
|
1814
|
+
|
|
1815
|
+
boolean success = com.android.tools.smali.smali.Smali.assemble(options, inputFiles);
|
|
1816
|
+
|
|
1817
|
+
if (!success) {
|
|
1818
|
+
result.put("success", false);
|
|
1819
|
+
result.put("error", "Smali 编译失败");
|
|
1820
|
+
cleanupTempDir(tempDir);
|
|
1821
|
+
return result;
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
Log.d(TAG, "Smali compiled successfully to: " + outputDex.getAbsolutePath());
|
|
1825
|
+
|
|
1826
|
+
// TODO: 将编译后的 dex 合并回原 APK
|
|
1827
|
+
// 这是一个复杂的操作,需要:
|
|
1828
|
+
// 1. 解压 APK
|
|
1829
|
+
// 2. 替换/合并 DEX 文件中的类
|
|
1830
|
+
// 3. 重新打包 APK
|
|
1831
|
+
// 4. 重新签名 APK
|
|
1832
|
+
|
|
1833
|
+
// 目前先返回成功,实际保存功能需要更多实现
|
|
1834
|
+
result.put("success", true);
|
|
1835
|
+
result.put("message", "Smali 编译成功,但完整保存功能需要进一步实现");
|
|
1836
|
+
result.put("compiledDexPath", outputDex.getAbsolutePath());
|
|
1837
|
+
|
|
1838
|
+
// 清理临时文件(暂时保留以便调试)
|
|
1839
|
+
// cleanupTempDir(tempDir);
|
|
1840
|
+
|
|
1841
|
+
} catch (Exception e) {
|
|
1842
|
+
Log.e(TAG, "Error saving smali: " + e.getMessage(), e);
|
|
1843
|
+
result.put("success", false);
|
|
1844
|
+
result.put("error", "保存失败: " + e.getMessage());
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
return result;
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
/**
|
|
1851
|
+
* 清理临时目录
|
|
1852
|
+
*/
|
|
1853
|
+
private void cleanupTempDir(java.io.File dir) {
|
|
1854
|
+
if (dir != null && dir.exists()) {
|
|
1855
|
+
java.io.File[] files = dir.listFiles();
|
|
1856
|
+
if (files != null) {
|
|
1857
|
+
for (java.io.File file : files) {
|
|
1858
|
+
if (file.isDirectory()) {
|
|
1859
|
+
cleanupTempDir(file);
|
|
1860
|
+
} else {
|
|
1861
|
+
file.delete();
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
dir.delete();
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1513
1868
|
}
|