capacitor-dex-editor 0.0.5 → 0.0.7

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.
@@ -1,11 +1,16 @@
1
1
  package com.aetherlink.dexeditor;
2
2
 
3
3
  import android.content.Context;
4
+ import android.content.Intent;
4
5
  import android.content.pm.PackageInfo;
5
6
  import android.content.pm.PackageManager;
6
7
  import android.content.pm.Signature;
8
+ import android.net.Uri;
9
+ import android.os.Build;
7
10
  import android.util.Log;
8
11
 
12
+ import androidx.core.content.FileProvider;
13
+
9
14
  import com.getcapacitor.JSArray;
10
15
  import com.getcapacitor.JSObject;
11
16
 
@@ -113,7 +118,7 @@ public class ApkManager {
113
118
  }
114
119
 
115
120
  /**
116
- * 获取 APK 信息 - 快速版本,直接解析二进制 AndroidManifest.xml
121
+ * 获取 APK 信息
117
122
  */
118
123
  public JSObject getApkInfo(String apkPath) throws Exception {
119
124
  File apkFile = new File(apkPath);
@@ -126,13 +131,32 @@ public class ApkManager {
126
131
  info.put("size", apkFile.length());
127
132
  info.put("lastModified", apkFile.lastModified());
128
133
 
129
- // 直接从 ZIP 读取并解析 AndroidManifest.xml(比 PackageManager 快很多)
134
+ // 获取包信息
135
+ if (context != null) {
136
+ PackageManager pm = context.getPackageManager();
137
+ PackageInfo packageInfo = pm.getPackageArchiveInfo(apkPath,
138
+ PackageManager.GET_META_DATA | PackageManager.GET_SIGNATURES);
139
+
140
+ if (packageInfo != null) {
141
+ info.put("packageName", packageInfo.packageName);
142
+ info.put("versionName", packageInfo.versionName);
143
+ info.put("versionCode", packageInfo.versionCode);
144
+
145
+ if (packageInfo.applicationInfo != null) {
146
+ packageInfo.applicationInfo.sourceDir = apkPath;
147
+ packageInfo.applicationInfo.publicSourceDir = apkPath;
148
+ CharSequence label = pm.getApplicationLabel(packageInfo.applicationInfo);
149
+ info.put("appName", label.toString());
150
+ }
151
+ }
152
+ }
153
+
154
+ // 列出 APK 内容
130
155
  try (ZipFile zipFile = new ZipFile(apkFile)) {
131
156
  int dexCount = 0;
132
157
  int resCount = 0;
133
158
  int libCount = 0;
134
159
 
135
- // 统计文件数量
136
160
  Enumeration<? extends ZipEntry> entries = zipFile.entries();
137
161
  while (entries.hasMoreElements()) {
138
162
  ZipEntry entry = entries.nextElement();
@@ -146,167 +170,10 @@ public class ApkManager {
146
170
  info.put("dexCount", dexCount);
147
171
  info.put("resourceCount", resCount);
148
172
  info.put("nativeLibCount", libCount);
149
-
150
- // 解析 AndroidManifest.xml
151
- ZipEntry manifestEntry = zipFile.getEntry("AndroidManifest.xml");
152
- if (manifestEntry != null) {
153
- try (InputStream is = zipFile.getInputStream(manifestEntry)) {
154
- byte[] data = readAllBytes(is);
155
- parseManifestBinary(data, info);
156
- }
157
- }
158
173
  }
159
174
 
160
175
  return info;
161
176
  }
162
-
163
- /**
164
- * 解析二进制格式的 AndroidManifest.xml
165
- */
166
- private void parseManifestBinary(byte[] data, JSObject info) {
167
- try {
168
- // 二进制 XML 格式解析
169
- if (data.length < 8) return;
170
-
171
- // 跳过 XML 头部,查找字符串池
172
- int offset = 8; // 跳过 magic number 和 file size
173
-
174
- // 读取字符串池
175
- if (offset + 8 > data.length) return;
176
- int stringPoolOffset = offset;
177
- int stringPoolType = readShort(data, offset);
178
- if (stringPoolType != 0x0001) return; // String Pool chunk type
179
-
180
- int stringPoolSize = readInt(data, offset + 4);
181
- int stringCount = readInt(data, offset + 8);
182
- int styleCount = readInt(data, offset + 12);
183
- int stringsStart = readInt(data, offset + 20);
184
-
185
- // 读取字符串偏移表
186
- int[] stringOffsets = new int[stringCount];
187
- int offsetTableStart = offset + 28;
188
- for (int i = 0; i < stringCount && offsetTableStart + i * 4 + 4 <= data.length; i++) {
189
- stringOffsets[i] = readInt(data, offsetTableStart + i * 4);
190
- }
191
-
192
- // 字符串数据起始位置
193
- int stringDataStart = stringPoolOffset + stringsStart;
194
-
195
- // 提取字符串池
196
- String[] strings = new String[stringCount];
197
- for (int i = 0; i < stringCount; i++) {
198
- int strOffset = stringDataStart + stringOffsets[i];
199
- if (strOffset + 2 > data.length) continue;
200
-
201
- // 检查是否 UTF-8 或 UTF-16
202
- int len = data[strOffset] & 0xFF;
203
- if (len == ((data[strOffset + 1] & 0xFF))) {
204
- // UTF-8
205
- strOffset += 2;
206
- if (strOffset + len <= data.length) {
207
- strings[i] = new String(data, strOffset, len, "UTF-8");
208
- }
209
- } else {
210
- // UTF-16
211
- int charLen = readShort(data, strOffset);
212
- strOffset += 2;
213
- if (strOffset + charLen * 2 <= data.length) {
214
- strings[i] = new String(data, strOffset, charLen * 2, "UTF-16LE");
215
- }
216
- }
217
- }
218
-
219
- // 在字符串池中查找关键信息
220
- String packageName = null;
221
- String versionName = null;
222
- String versionCode = null;
223
- String appLabel = null;
224
-
225
- for (int i = 0; i < strings.length; i++) {
226
- String s = strings[i];
227
- if (s == null) continue;
228
-
229
- // 查找 package 属性的值(通常在 manifest 标签后)
230
- if (s.matches("^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$")) {
231
- if (packageName == null) packageName = s;
232
- }
233
- // 版本名通常包含数字和点
234
- if (s.matches("^\\d+\\.\\d+.*$") || s.matches("^v?\\d+\\.\\d+.*$")) {
235
- if (versionName == null) versionName = s;
236
- }
237
- }
238
-
239
- // 更精确的解析:遍历 XML 元素
240
- offset = stringPoolOffset + stringPoolSize;
241
- while (offset + 8 < data.length) {
242
- int chunkType = readShort(data, offset);
243
- int chunkSize = readInt(data, offset + 4);
244
-
245
- if (chunkType == 0x0102) { // START_TAG
246
- int attrStart = offset + 28;
247
- int attrCount = readShort(data, offset + 24);
248
-
249
- for (int i = 0; i < attrCount && attrStart + 20 <= data.length; i++) {
250
- int nameIdx = readInt(data, attrStart + 4);
251
- int valueIdx = readInt(data, attrStart + 8);
252
- int valueType = data[attrStart + 15] & 0xFF;
253
- int valueData = readInt(data, attrStart + 16);
254
-
255
- if (nameIdx >= 0 && nameIdx < strings.length) {
256
- String attrName = strings[nameIdx];
257
- if ("package".equals(attrName) && valueIdx >= 0 && valueIdx < strings.length) {
258
- packageName = strings[valueIdx];
259
- } else if ("versionName".equals(attrName) && valueIdx >= 0 && valueIdx < strings.length) {
260
- versionName = strings[valueIdx];
261
- } else if ("versionCode".equals(attrName)) {
262
- if (valueType == 0x10) { // TYPE_INT_DEC
263
- versionCode = String.valueOf(valueData);
264
- }
265
- } else if ("label".equals(attrName) && valueIdx >= 0 && valueIdx < strings.length) {
266
- String label = strings[valueIdx];
267
- if (label != null && !label.startsWith("@")) {
268
- appLabel = label;
269
- }
270
- }
271
- }
272
- attrStart += 20;
273
- }
274
- }
275
-
276
- if (chunkSize <= 0) break;
277
- offset += chunkSize;
278
- }
279
-
280
- if (packageName != null) info.put("packageName", packageName);
281
- if (versionName != null) info.put("versionName", versionName);
282
- if (versionCode != null) info.put("versionCode", Integer.parseInt(versionCode));
283
- if (appLabel != null) info.put("appName", appLabel);
284
-
285
- } catch (Exception e) {
286
- Log.w(TAG, "Failed to parse manifest binary: " + e.getMessage());
287
- }
288
- }
289
-
290
- private int readShort(byte[] data, int offset) {
291
- return (data[offset] & 0xFF) | ((data[offset + 1] & 0xFF) << 8);
292
- }
293
-
294
- private int readInt(byte[] data, int offset) {
295
- return (data[offset] & 0xFF) |
296
- ((data[offset + 1] & 0xFF) << 8) |
297
- ((data[offset + 2] & 0xFF) << 16) |
298
- ((data[offset + 3] & 0xFF) << 24);
299
- }
300
-
301
- private byte[] readAllBytes(InputStream is) throws IOException {
302
- java.io.ByteArrayOutputStream buffer = new java.io.ByteArrayOutputStream();
303
- byte[] data = new byte[BUFFER_SIZE];
304
- int len;
305
- while ((len = is.read(data)) != -1) {
306
- buffer.write(data, 0, len);
307
- }
308
- return buffer.toByteArray();
309
- }
310
177
 
311
178
  /**
312
179
  * 列出 APK 内容
@@ -503,6 +370,145 @@ public class ApkManager {
503
370
  return result;
504
371
  }
505
372
 
373
+ /**
374
+ * 安装 APK
375
+ */
376
+ public void installApk(String apkPath) throws Exception {
377
+ if (context == null) {
378
+ throw new Exception("Context not available");
379
+ }
380
+
381
+ File apkFile = new File(apkPath);
382
+ if (!apkFile.exists()) {
383
+ throw new IOException("APK file not found: " + apkPath);
384
+ }
385
+
386
+ Intent intent = new Intent(Intent.ACTION_VIEW);
387
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
388
+
389
+ Uri apkUri;
390
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
391
+ // Android 7.0+ 需要使用 FileProvider
392
+ String authority = context.getPackageName() + ".fileprovider";
393
+ apkUri = FileProvider.getUriForFile(context, authority, apkFile);
394
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
395
+ } else {
396
+ apkUri = Uri.fromFile(apkFile);
397
+ }
398
+
399
+ intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
400
+ context.startActivity(intent);
401
+
402
+ Log.d(TAG, "Started APK installation: " + apkPath);
403
+ }
404
+
405
+ /**
406
+ * 列出 APK 指定目录的内容(支持目录导航)
407
+ */
408
+ public JSObject listApkDirectory(String apkPath, String directory) throws Exception {
409
+ File apkFile = new File(apkPath);
410
+ if (!apkFile.exists()) {
411
+ throw new IOException("APK file not found: " + apkPath);
412
+ }
413
+
414
+ // 规范化目录路径
415
+ if (directory == null) directory = "";
416
+ if (directory.startsWith("/")) directory = directory.substring(1);
417
+ if (!directory.isEmpty() && !directory.endsWith("/")) directory += "/";
418
+
419
+ JSObject result = new JSObject();
420
+ result.put("currentPath", directory.isEmpty() ? "/" : "/" + directory);
421
+
422
+ JSArray items = new JSArray();
423
+ int folderCount = 0;
424
+ int fileCount = 0;
425
+
426
+ // 用于跟踪已添加的目录
427
+ java.util.Set<String> addedDirs = new java.util.HashSet<>();
428
+
429
+ try (ZipFile zipFile = new ZipFile(apkFile)) {
430
+ Enumeration<? extends ZipEntry> entries = zipFile.entries();
431
+ while (entries.hasMoreElements()) {
432
+ ZipEntry entry = entries.nextElement();
433
+ String name = entry.getName();
434
+
435
+ // 如果指定了目录,只显示该目录下的内容
436
+ if (!directory.isEmpty()) {
437
+ if (!name.startsWith(directory)) continue;
438
+ name = name.substring(directory.length());
439
+ }
440
+
441
+ if (name.isEmpty()) continue;
442
+
443
+ // 检查是否是直接子项(不包含更深层的路径)
444
+ int slashIndex = name.indexOf('/');
445
+ boolean isDirectChild = slashIndex == -1 || slashIndex == name.length() - 1;
446
+
447
+ if (isDirectChild) {
448
+ // 直接子项
449
+ JSObject item = new JSObject();
450
+ String displayName = name.endsWith("/") ? name.substring(0, name.length() - 1) : name;
451
+ item.put("name", displayName);
452
+ item.put("path", directory + name);
453
+ item.put("isDirectory", entry.isDirectory());
454
+ item.put("size", entry.getSize());
455
+ item.put("compressedSize", entry.getCompressedSize());
456
+ item.put("lastModified", entry.getTime());
457
+
458
+ // 根据文件类型设置图标类型
459
+ item.put("type", getFileType(displayName, entry.isDirectory()));
460
+
461
+ items.put(item);
462
+ if (entry.isDirectory()) folderCount++;
463
+ else fileCount++;
464
+ } else {
465
+ // 子目录中的文件,添加其父目录
466
+ String dirName = name.substring(0, slashIndex);
467
+ if (!addedDirs.contains(dirName)) {
468
+ addedDirs.add(dirName);
469
+
470
+ JSObject item = new JSObject();
471
+ item.put("name", dirName);
472
+ item.put("path", directory + dirName + "/");
473
+ item.put("isDirectory", true);
474
+ item.put("size", 0);
475
+ item.put("type", "folder");
476
+
477
+ items.put(item);
478
+ folderCount++;
479
+ }
480
+ }
481
+ }
482
+ }
483
+
484
+ result.put("items", items);
485
+ result.put("folderCount", folderCount);
486
+ result.put("fileCount", fileCount);
487
+
488
+ return result;
489
+ }
490
+
491
+ /**
492
+ * 获取文件类型
493
+ */
494
+ private String getFileType(String name, boolean isDirectory) {
495
+ if (isDirectory) return "folder";
496
+
497
+ String lowerName = name.toLowerCase();
498
+ if (lowerName.endsWith(".dex")) return "dex";
499
+ if (lowerName.endsWith(".xml")) return "xml";
500
+ if (lowerName.endsWith(".arsc")) return "resource";
501
+ if (lowerName.endsWith(".so")) return "native";
502
+ if (lowerName.endsWith(".png") || lowerName.endsWith(".jpg") ||
503
+ lowerName.endsWith(".jpeg") || lowerName.endsWith(".webp") ||
504
+ lowerName.endsWith(".gif")) return "image";
505
+ if (lowerName.endsWith(".smali")) return "smali";
506
+ if (lowerName.endsWith(".bin") || lowerName.endsWith(".dat")) return "binary";
507
+ if (lowerName.equals("androidmanifest.xml")) return "manifest";
508
+
509
+ return "file";
510
+ }
511
+
506
512
  /**
507
513
  * 关闭 APK 会话
508
514
  */
@@ -390,6 +390,40 @@ public class DexEditorPluginPlugin extends Plugin {
390
390
  result.put("data", apkManager.getSessionDexFiles(params.getString("sessionId")));
391
391
  break;
392
392
 
393
+ case "installApk":
394
+ apkManager.installApk(params.getString("apkPath"));
395
+ break;
396
+
397
+ case "listApkDirectory":
398
+ result.put("data", apkManager.listApkDirectory(
399
+ params.getString("apkPath"),
400
+ params.optString("directory", "")
401
+ ));
402
+ break;
403
+
404
+ // ==================== DEX 编辑器操作 ====================
405
+ case "listDexClasses":
406
+ result.put("data", dexManager.listDexClassesFromApk(
407
+ params.getString("apkPath"),
408
+ params.getString("dexPath")
409
+ ));
410
+ break;
411
+
412
+ case "getDexStrings":
413
+ result.put("data", dexManager.getDexStringsFromApk(
414
+ params.getString("apkPath"),
415
+ params.getString("dexPath")
416
+ ));
417
+ break;
418
+
419
+ case "searchInDex":
420
+ result.put("data", dexManager.searchInDexFromApk(
421
+ params.getString("apkPath"),
422
+ params.getString("dexPath"),
423
+ params.getString("query")
424
+ ));
425
+ break;
426
+
393
427
  default:
394
428
  result.put("success", false);
395
429
  result.put("error", "Unknown action: " + action);
@@ -1214,4 +1214,253 @@ public class DexManager {
1214
1214
  }
1215
1215
  return count;
1216
1216
  }
1217
+
1218
+ // ==================== APK 内 DEX 操作(无需会话) ====================
1219
+
1220
+ /**
1221
+ * 从 APK 中的 DEX 文件列出所有类
1222
+ * @param apkPath APK 文件路径
1223
+ * @param dexPath DEX 文件在 APK 中的路径(如 "classes.dex")
1224
+ */
1225
+ public JSObject listDexClassesFromApk(String apkPath, String dexPath) throws Exception {
1226
+ JSObject result = new JSObject();
1227
+ JSArray classes = new JSArray();
1228
+
1229
+ java.util.zip.ZipFile zipFile = null;
1230
+ java.io.InputStream dexInputStream = null;
1231
+
1232
+ try {
1233
+ zipFile = new java.util.zip.ZipFile(apkPath);
1234
+ java.util.zip.ZipEntry dexEntry = zipFile.getEntry(dexPath);
1235
+
1236
+ if (dexEntry == null) {
1237
+ throw new IOException("DEX file not found in APK: " + dexPath);
1238
+ }
1239
+
1240
+ dexInputStream = zipFile.getInputStream(dexEntry);
1241
+
1242
+ // 读取 DEX 文件到内存
1243
+ java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
1244
+ byte[] buffer = new byte[8192];
1245
+ int len;
1246
+ while ((len = dexInputStream.read(buffer)) != -1) {
1247
+ baos.write(buffer, 0, len);
1248
+ }
1249
+ byte[] dexBytes = baos.toByteArray();
1250
+
1251
+ // 解析 DEX 文件
1252
+ DexBackedDexFile dexFile = new DexBackedDexFile(Opcodes.getDefault(), dexBytes);
1253
+
1254
+ // 收集所有类名
1255
+ for (ClassDef classDef : dexFile.getClasses()) {
1256
+ String type = classDef.getType();
1257
+ // 转换 Lcom/example/Class; 格式为 com.example.Class
1258
+ String className = convertTypeToClassName(type);
1259
+ classes.put(className);
1260
+ }
1261
+
1262
+ result.put("classes", classes);
1263
+ result.put("count", classes.length());
1264
+
1265
+ } finally {
1266
+ if (dexInputStream != null) {
1267
+ try { dexInputStream.close(); } catch (Exception ignored) {}
1268
+ }
1269
+ if (zipFile != null) {
1270
+ try { zipFile.close(); } catch (Exception ignored) {}
1271
+ }
1272
+ }
1273
+
1274
+ return result;
1275
+ }
1276
+
1277
+ /**
1278
+ * 从 APK 中的 DEX 文件获取字符串常量池
1279
+ */
1280
+ public JSObject getDexStringsFromApk(String apkPath, String dexPath) throws Exception {
1281
+ JSObject result = new JSObject();
1282
+ JSArray strings = new JSArray();
1283
+
1284
+ java.util.zip.ZipFile zipFile = null;
1285
+ java.io.InputStream dexInputStream = null;
1286
+
1287
+ try {
1288
+ zipFile = new java.util.zip.ZipFile(apkPath);
1289
+ java.util.zip.ZipEntry dexEntry = zipFile.getEntry(dexPath);
1290
+
1291
+ if (dexEntry == null) {
1292
+ throw new IOException("DEX file not found in APK: " + dexPath);
1293
+ }
1294
+
1295
+ dexInputStream = zipFile.getInputStream(dexEntry);
1296
+
1297
+ // 读取 DEX 文件到内存
1298
+ java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
1299
+ byte[] buffer = new byte[8192];
1300
+ int len;
1301
+ while ((len = dexInputStream.read(buffer)) != -1) {
1302
+ baos.write(buffer, 0, len);
1303
+ }
1304
+ byte[] dexBytes = baos.toByteArray();
1305
+
1306
+ // 解析 DEX 文件
1307
+ DexBackedDexFile dexFile = new DexBackedDexFile(Opcodes.getDefault(), dexBytes);
1308
+
1309
+ // 收集字符串
1310
+ Set<String> uniqueStrings = new HashSet<>();
1311
+ int index = 0;
1312
+
1313
+ for (ClassDef classDef : dexFile.getClasses()) {
1314
+ // 从方法中提取字符串
1315
+ for (Method method : classDef.getMethods()) {
1316
+ MethodImplementation impl = method.getImplementation();
1317
+ if (impl != null) {
1318
+ for (Instruction instruction : impl.getInstructions()) {
1319
+ String instrStr = instruction.toString();
1320
+ // 提取字符串常量
1321
+ if (instrStr.contains("\"")) {
1322
+ int start = instrStr.indexOf("\"");
1323
+ int end = instrStr.lastIndexOf("\"");
1324
+ if (start != -1 && end > start) {
1325
+ String str = instrStr.substring(start + 1, end);
1326
+ if (!uniqueStrings.contains(str)) {
1327
+ uniqueStrings.add(str);
1328
+ JSObject item = new JSObject();
1329
+ item.put("index", index++);
1330
+ item.put("value", str);
1331
+ strings.put(item);
1332
+ }
1333
+ }
1334
+ }
1335
+ }
1336
+ }
1337
+ }
1338
+ }
1339
+
1340
+ result.put("strings", strings);
1341
+ result.put("count", strings.length());
1342
+
1343
+ } finally {
1344
+ if (dexInputStream != null) {
1345
+ try { dexInputStream.close(); } catch (Exception ignored) {}
1346
+ }
1347
+ if (zipFile != null) {
1348
+ try { zipFile.close(); } catch (Exception ignored) {}
1349
+ }
1350
+ }
1351
+
1352
+ return result;
1353
+ }
1354
+
1355
+ /**
1356
+ * 在 APK 中的 DEX 文件中搜索
1357
+ */
1358
+ public JSObject searchInDexFromApk(String apkPath, String dexPath, String query) throws Exception {
1359
+ JSObject result = new JSObject();
1360
+ JSArray results = new JSArray();
1361
+
1362
+ if (query == null || query.isEmpty()) {
1363
+ result.put("results", results);
1364
+ result.put("count", 0);
1365
+ return result;
1366
+ }
1367
+
1368
+ String queryLower = query.toLowerCase();
1369
+
1370
+ java.util.zip.ZipFile zipFile = null;
1371
+ java.io.InputStream dexInputStream = null;
1372
+
1373
+ try {
1374
+ zipFile = new java.util.zip.ZipFile(apkPath);
1375
+ java.util.zip.ZipEntry dexEntry = zipFile.getEntry(dexPath);
1376
+
1377
+ if (dexEntry == null) {
1378
+ throw new IOException("DEX file not found in APK: " + dexPath);
1379
+ }
1380
+
1381
+ dexInputStream = zipFile.getInputStream(dexEntry);
1382
+
1383
+ // 读取 DEX 文件到内存
1384
+ java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
1385
+ byte[] buffer = new byte[8192];
1386
+ int len;
1387
+ while ((len = dexInputStream.read(buffer)) != -1) {
1388
+ baos.write(buffer, 0, len);
1389
+ }
1390
+ byte[] dexBytes = baos.toByteArray();
1391
+
1392
+ // 解析 DEX 文件
1393
+ DexBackedDexFile dexFile = new DexBackedDexFile(Opcodes.getDefault(), dexBytes);
1394
+
1395
+ // 搜索类名和方法名
1396
+ for (ClassDef classDef : dexFile.getClasses()) {
1397
+ String className = convertTypeToClassName(classDef.getType());
1398
+
1399
+ // 搜索类名
1400
+ if (className.toLowerCase().contains(queryLower)) {
1401
+ JSObject item = new JSObject();
1402
+ item.put("className", className);
1403
+ item.put("type", "class");
1404
+ item.put("content", className);
1405
+ results.put(item);
1406
+ }
1407
+
1408
+ // 搜索方法名
1409
+ for (Method method : classDef.getMethods()) {
1410
+ String methodName = method.getName();
1411
+ if (methodName.toLowerCase().contains(queryLower)) {
1412
+ JSObject item = new JSObject();
1413
+ item.put("className", className);
1414
+ item.put("methodName", methodName);
1415
+ item.put("type", "method");
1416
+ item.put("content", methodName + " in " + className);
1417
+ results.put(item);
1418
+ }
1419
+
1420
+ // 搜索方法内的字符串
1421
+ MethodImplementation impl = method.getImplementation();
1422
+ if (impl != null) {
1423
+ for (Instruction instruction : impl.getInstructions()) {
1424
+ String instrStr = instruction.toString();
1425
+ if (instrStr.toLowerCase().contains(queryLower)) {
1426
+ JSObject item = new JSObject();
1427
+ item.put("className", className);
1428
+ item.put("methodName", methodName);
1429
+ item.put("type", "instruction");
1430
+ item.put("content", instrStr);
1431
+ results.put(item);
1432
+ break; // 每个方法只记录一次
1433
+ }
1434
+ }
1435
+ }
1436
+ }
1437
+ }
1438
+
1439
+ result.put("results", results);
1440
+ result.put("count", results.length());
1441
+
1442
+ } finally {
1443
+ if (dexInputStream != null) {
1444
+ try { dexInputStream.close(); } catch (Exception ignored) {}
1445
+ }
1446
+ if (zipFile != null) {
1447
+ try { zipFile.close(); } catch (Exception ignored) {}
1448
+ }
1449
+ }
1450
+
1451
+ return result;
1452
+ }
1453
+
1454
+ /**
1455
+ * 将 DEX 类型格式转换为 Java 类名格式
1456
+ * 例如: Lcom/example/Class; -> com.example.Class
1457
+ */
1458
+ private String convertTypeToClassName(String type) {
1459
+ if (type == null) return "";
1460
+ String className = type;
1461
+ if (className.startsWith("L") && className.endsWith(";")) {
1462
+ className = className.substring(1, className.length() - 1);
1463
+ }
1464
+ return className.replace("/", ".");
1465
+ }
1217
1466
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "capacitor-dex-editor",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
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",