capacitor-dex-editor 0.0.59 → 0.0.61
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.
|
@@ -601,10 +601,32 @@ public class DexEditorPluginPlugin extends Plugin {
|
|
|
601
601
|
));
|
|
602
602
|
break;
|
|
603
603
|
|
|
604
|
+
case "deleteFileFromApk":
|
|
605
|
+
result.put("data", dexManager.deleteFileFromApk(
|
|
606
|
+
params.getString("apkPath"),
|
|
607
|
+
params.getString("filePath")
|
|
608
|
+
));
|
|
609
|
+
break;
|
|
610
|
+
|
|
611
|
+
case "addFileToApk":
|
|
612
|
+
result.put("data", dexManager.addFileToApk(
|
|
613
|
+
params.getString("apkPath"),
|
|
614
|
+
params.getString("filePath"),
|
|
615
|
+
params.getString("content"),
|
|
616
|
+
params.optBoolean("isBase64", false)
|
|
617
|
+
));
|
|
618
|
+
break;
|
|
619
|
+
|
|
604
620
|
case "listSessions":
|
|
605
621
|
result.put("data", dexManager.listAllSessions());
|
|
606
622
|
break;
|
|
607
623
|
|
|
624
|
+
case "precompileSmaliCache":
|
|
625
|
+
result.put("data", dexManager.precompileSmaliCache(
|
|
626
|
+
params.getString("sessionId")
|
|
627
|
+
));
|
|
628
|
+
break;
|
|
629
|
+
|
|
608
630
|
// ==================== XML/资源操作 ====================
|
|
609
631
|
case "getManifest":
|
|
610
632
|
result.put("data", dexManager.getManifestFromApk(
|
|
@@ -151,6 +151,8 @@ public class DexManager {
|
|
|
151
151
|
String apkPath;
|
|
152
152
|
Map<String, DexBackedDexFile> dexFiles;
|
|
153
153
|
Map<String, ClassDef> modifiedClasses;
|
|
154
|
+
Map<String, String> smaliCache; // className -> smali 代码缓存
|
|
155
|
+
boolean smaliCacheReady = false;
|
|
154
156
|
boolean modified = false;
|
|
155
157
|
|
|
156
158
|
MultiDexSession(String sessionId, String apkPath) {
|
|
@@ -158,6 +160,7 @@ public class DexManager {
|
|
|
158
160
|
this.apkPath = apkPath;
|
|
159
161
|
this.dexFiles = new HashMap<>();
|
|
160
162
|
this.modifiedClasses = new HashMap<>();
|
|
163
|
+
this.smaliCache = new HashMap<>();
|
|
161
164
|
}
|
|
162
165
|
|
|
163
166
|
void addDex(String dexName, DexBackedDexFile dexFile) {
|
|
@@ -1830,8 +1833,8 @@ public class DexManager {
|
|
|
1830
1833
|
|
|
1831
1834
|
case "string":
|
|
1832
1835
|
case "code":
|
|
1833
|
-
//
|
|
1834
|
-
String smali =
|
|
1836
|
+
// 使用缓存的 smali 搜索
|
|
1837
|
+
String smali = getCachedSmali(session, className, dexFile, classDef);
|
|
1835
1838
|
String smaliMatch = caseSensitive ? smali : smali.toLowerCase();
|
|
1836
1839
|
if (smaliMatch.contains(queryMatch)) {
|
|
1837
1840
|
JSObject item = new JSObject();
|
|
@@ -1853,8 +1856,8 @@ public class DexManager {
|
|
|
1853
1856
|
break;
|
|
1854
1857
|
|
|
1855
1858
|
case "int":
|
|
1856
|
-
//
|
|
1857
|
-
String smaliForInt =
|
|
1859
|
+
// 搜索整数常量(使用缓存)
|
|
1860
|
+
String smaliForInt = getCachedSmali(session, className, dexFile, classDef);
|
|
1858
1861
|
if (smaliForInt.contains("0x" + query) || smaliForInt.contains(" " + query + "\n") ||
|
|
1859
1862
|
smaliForInt.contains(" " + query + " ")) {
|
|
1860
1863
|
JSObject item = new JSObject();
|
|
@@ -1893,6 +1896,62 @@ public class DexManager {
|
|
|
1893
1896
|
}
|
|
1894
1897
|
}
|
|
1895
1898
|
|
|
1899
|
+
/**
|
|
1900
|
+
* 获取缓存的 Smali 代码(用于搜索优化)
|
|
1901
|
+
*/
|
|
1902
|
+
private String getCachedSmali(MultiDexSession session, String className, DexBackedDexFile dexFile, ClassDef classDef) {
|
|
1903
|
+
// 先查缓存
|
|
1904
|
+
String cached = session.smaliCache.get(className);
|
|
1905
|
+
if (cached != null) {
|
|
1906
|
+
return cached;
|
|
1907
|
+
}
|
|
1908
|
+
// 缓存未命中,反编译并缓存
|
|
1909
|
+
String smali = getSmaliForClass(dexFile, classDef);
|
|
1910
|
+
session.smaliCache.put(className, smali);
|
|
1911
|
+
return smali;
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
/**
|
|
1915
|
+
* 预编译会话中所有类的 Smali(用于加速搜索)
|
|
1916
|
+
*/
|
|
1917
|
+
public JSObject precompileSmaliCache(String sessionId) throws Exception {
|
|
1918
|
+
MultiDexSession session = multiDexSessions.get(sessionId);
|
|
1919
|
+
if (session == null) {
|
|
1920
|
+
throw new IllegalArgumentException("Session not found: " + sessionId);
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
if (session.smaliCacheReady) {
|
|
1924
|
+
JSObject result = new JSObject();
|
|
1925
|
+
result.put("cached", true);
|
|
1926
|
+
result.put("count", session.smaliCache.size());
|
|
1927
|
+
return result;
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
int count = 0;
|
|
1931
|
+
long startTime = System.currentTimeMillis();
|
|
1932
|
+
|
|
1933
|
+
for (Map.Entry<String, DexBackedDexFile> entry : session.dexFiles.entrySet()) {
|
|
1934
|
+
DexBackedDexFile dexFile = entry.getValue();
|
|
1935
|
+
for (ClassDef classDef : dexFile.getClasses()) {
|
|
1936
|
+
String className = convertTypeToClassName(classDef.getType());
|
|
1937
|
+
if (!session.smaliCache.containsKey(className)) {
|
|
1938
|
+
String smali = getSmaliForClass(dexFile, classDef);
|
|
1939
|
+
session.smaliCache.put(className, smali);
|
|
1940
|
+
count++;
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
session.smaliCacheReady = true;
|
|
1946
|
+
long elapsed = System.currentTimeMillis() - startTime;
|
|
1947
|
+
|
|
1948
|
+
JSObject result = new JSObject();
|
|
1949
|
+
result.put("success", true);
|
|
1950
|
+
result.put("count", count);
|
|
1951
|
+
result.put("elapsedMs", elapsed);
|
|
1952
|
+
return result;
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1896
1955
|
/**
|
|
1897
1956
|
* 从多 DEX 会话获取类的 Smali 代码
|
|
1898
1957
|
*/
|
|
@@ -2332,6 +2391,156 @@ public class DexManager {
|
|
|
2332
2391
|
return result;
|
|
2333
2392
|
}
|
|
2334
2393
|
|
|
2394
|
+
/**
|
|
2395
|
+
* 从 APK 中删除指定文件
|
|
2396
|
+
*/
|
|
2397
|
+
public JSObject deleteFileFromApk(String apkPath, String filePath) throws Exception {
|
|
2398
|
+
JSObject result = new JSObject();
|
|
2399
|
+
|
|
2400
|
+
java.io.File apkFile = new java.io.File(apkPath);
|
|
2401
|
+
java.io.File tempApkFile = new java.io.File(apkPath + ".tmp");
|
|
2402
|
+
|
|
2403
|
+
java.util.zip.ZipInputStream zis = new java.util.zip.ZipInputStream(new java.io.FileInputStream(apkFile));
|
|
2404
|
+
java.util.zip.ZipOutputStream zos = new java.util.zip.ZipOutputStream(new java.io.FileOutputStream(tempApkFile));
|
|
2405
|
+
|
|
2406
|
+
java.util.zip.ZipEntry entry;
|
|
2407
|
+
boolean found = false;
|
|
2408
|
+
String normalizedPath = filePath.replaceFirst("^/+", "");
|
|
2409
|
+
|
|
2410
|
+
while ((entry = zis.getNextEntry()) != null) {
|
|
2411
|
+
String entryName = entry.getName();
|
|
2412
|
+
|
|
2413
|
+
if (entryName.equals(filePath) || entryName.equals(normalizedPath)) {
|
|
2414
|
+
// 跳过要删除的文件
|
|
2415
|
+
found = true;
|
|
2416
|
+
continue;
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2419
|
+
// 复制其他文件
|
|
2420
|
+
java.util.zip.ZipEntry newEntry = new java.util.zip.ZipEntry(entryName);
|
|
2421
|
+
if (entry.getMethod() == java.util.zip.ZipEntry.STORED) {
|
|
2422
|
+
newEntry.setMethod(java.util.zip.ZipEntry.STORED);
|
|
2423
|
+
newEntry.setSize(entry.getSize());
|
|
2424
|
+
newEntry.setCrc(entry.getCrc());
|
|
2425
|
+
}
|
|
2426
|
+
zos.putNextEntry(newEntry);
|
|
2427
|
+
|
|
2428
|
+
byte[] buffer = new byte[8192];
|
|
2429
|
+
int len;
|
|
2430
|
+
while ((len = zis.read(buffer)) > 0) {
|
|
2431
|
+
zos.write(buffer, 0, len);
|
|
2432
|
+
}
|
|
2433
|
+
zos.closeEntry();
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
zis.close();
|
|
2437
|
+
zos.close();
|
|
2438
|
+
|
|
2439
|
+
if (!found) {
|
|
2440
|
+
tempApkFile.delete();
|
|
2441
|
+
result.put("success", false);
|
|
2442
|
+
result.put("error", "文件未找到: " + filePath);
|
|
2443
|
+
return result;
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2446
|
+
// 替换原文件
|
|
2447
|
+
if (!apkFile.delete()) {
|
|
2448
|
+
tempApkFile.delete();
|
|
2449
|
+
result.put("success", false);
|
|
2450
|
+
result.put("error", "无法删除原 APK");
|
|
2451
|
+
return result;
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
if (!tempApkFile.renameTo(apkFile)) {
|
|
2455
|
+
copyFile(tempApkFile, apkFile);
|
|
2456
|
+
tempApkFile.delete();
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
result.put("success", true);
|
|
2460
|
+
result.put("message", "文件已删除: " + filePath);
|
|
2461
|
+
result.put("needSign", true);
|
|
2462
|
+
return result;
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2465
|
+
/**
|
|
2466
|
+
* 向 APK 中添加或替换文件
|
|
2467
|
+
*/
|
|
2468
|
+
public JSObject addFileToApk(String apkPath, String filePath, String content, boolean isBase64) throws Exception {
|
|
2469
|
+
JSObject result = new JSObject();
|
|
2470
|
+
|
|
2471
|
+
// 解码内容
|
|
2472
|
+
byte[] contentBytes;
|
|
2473
|
+
if (isBase64) {
|
|
2474
|
+
contentBytes = android.util.Base64.decode(content, android.util.Base64.DEFAULT);
|
|
2475
|
+
} else {
|
|
2476
|
+
contentBytes = content.getBytes("UTF-8");
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
java.io.File apkFile = new java.io.File(apkPath);
|
|
2480
|
+
java.io.File tempApkFile = new java.io.File(apkPath + ".tmp");
|
|
2481
|
+
|
|
2482
|
+
java.util.zip.ZipInputStream zis = new java.util.zip.ZipInputStream(new java.io.FileInputStream(apkFile));
|
|
2483
|
+
java.util.zip.ZipOutputStream zos = new java.util.zip.ZipOutputStream(new java.io.FileOutputStream(tempApkFile));
|
|
2484
|
+
|
|
2485
|
+
java.util.zip.ZipEntry entry;
|
|
2486
|
+
String normalizedPath = filePath.replaceFirst("^/+", "");
|
|
2487
|
+
boolean replaced = false;
|
|
2488
|
+
|
|
2489
|
+
while ((entry = zis.getNextEntry()) != null) {
|
|
2490
|
+
String entryName = entry.getName();
|
|
2491
|
+
|
|
2492
|
+
if (entryName.equals(filePath) || entryName.equals(normalizedPath)) {
|
|
2493
|
+
// 跳过要替换的文件,稍后添加新版本
|
|
2494
|
+
replaced = true;
|
|
2495
|
+
continue;
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
// 复制其他文件
|
|
2499
|
+
java.util.zip.ZipEntry newEntry = new java.util.zip.ZipEntry(entryName);
|
|
2500
|
+
if (entry.getMethod() == java.util.zip.ZipEntry.STORED) {
|
|
2501
|
+
newEntry.setMethod(java.util.zip.ZipEntry.STORED);
|
|
2502
|
+
newEntry.setSize(entry.getSize());
|
|
2503
|
+
newEntry.setCrc(entry.getCrc());
|
|
2504
|
+
}
|
|
2505
|
+
zos.putNextEntry(newEntry);
|
|
2506
|
+
|
|
2507
|
+
byte[] buffer = new byte[8192];
|
|
2508
|
+
int len;
|
|
2509
|
+
while ((len = zis.read(buffer)) > 0) {
|
|
2510
|
+
zos.write(buffer, 0, len);
|
|
2511
|
+
}
|
|
2512
|
+
zos.closeEntry();
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
// 添加新文件
|
|
2516
|
+
java.util.zip.ZipEntry newEntry = new java.util.zip.ZipEntry(normalizedPath);
|
|
2517
|
+
newEntry.setSize(contentBytes.length);
|
|
2518
|
+
zos.putNextEntry(newEntry);
|
|
2519
|
+
zos.write(contentBytes);
|
|
2520
|
+
zos.closeEntry();
|
|
2521
|
+
|
|
2522
|
+
zis.close();
|
|
2523
|
+
zos.close();
|
|
2524
|
+
|
|
2525
|
+
// 替换原文件
|
|
2526
|
+
if (!apkFile.delete()) {
|
|
2527
|
+
tempApkFile.delete();
|
|
2528
|
+
result.put("success", false);
|
|
2529
|
+
result.put("error", "无法删除原 APK");
|
|
2530
|
+
return result;
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
if (!tempApkFile.renameTo(apkFile)) {
|
|
2534
|
+
copyFile(tempApkFile, apkFile);
|
|
2535
|
+
tempApkFile.delete();
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
result.put("success", true);
|
|
2539
|
+
result.put("message", replaced ? "文件已替换: " + filePath : "文件已添加: " + filePath);
|
|
2540
|
+
result.put("needSign", true);
|
|
2541
|
+
return result;
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2335
2544
|
/**
|
|
2336
2545
|
* 保存多 DEX 会话的修改到 APK
|
|
2337
2546
|
*/
|