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
- // 需要反编译 smali 搜索
1834
- String smali = getSmaliForClass(dexFile, classDef);
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 = getSmaliForClass(dexFile, classDef);
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
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "capacitor-dex-editor",
3
- "version": "0.0.59",
3
+ "version": "0.0.61",
4
4
  "description": "Capacitor-plugin-for-editing-DEX-files-in-APK",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",