capacitor-dex-editor 0.0.67 → 0.0.69

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.
@@ -665,6 +665,18 @@ public class DexEditorPluginPlugin extends Plugin {
665
665
  ));
666
666
  break;
667
667
 
668
+ case "searchTextInApk":
669
+ result.put("data", dexManager.searchTextInApk(
670
+ params.getString("apkPath"),
671
+ params.getString("pattern"),
672
+ params.optJSONArray("fileExtensions"),
673
+ params.optBoolean("caseSensitive", false),
674
+ params.optBoolean("isRegex", false),
675
+ params.optInt("maxResults", 50),
676
+ params.optInt("contextLines", 2)
677
+ ));
678
+ break;
679
+
668
680
  case "readApkFile":
669
681
  result.put("data", dexManager.readApkFile(
670
682
  params.getString("apkPath"),
@@ -3676,6 +3676,136 @@ public class DexManager {
3676
3676
  return result;
3677
3677
  }
3678
3678
 
3679
+ /**
3680
+ * 在 APK 中搜索文本内容
3681
+ */
3682
+ public JSObject searchTextInApk(String apkPath, String pattern, org.json.JSONArray fileExtensions,
3683
+ boolean caseSensitive, boolean isRegex, int maxResults, int contextLines) throws Exception {
3684
+ JSObject result = new JSObject();
3685
+ JSArray results = new JSArray();
3686
+
3687
+ // 二进制文件扩展名(跳过)
3688
+ java.util.Set<String> binaryExtensions = new java.util.HashSet<>(java.util.Arrays.asList(
3689
+ ".dex", ".so", ".png", ".jpg", ".jpeg", ".gif", ".webp", ".ico",
3690
+ ".zip", ".apk", ".jar", ".class", ".ogg", ".mp3", ".wav", ".mp4",
3691
+ ".arsc", ".9.png", ".ttf", ".otf", ".woff"
3692
+ ));
3693
+
3694
+ // 解析文件扩展名过滤
3695
+ java.util.Set<String> allowedExtensions = new java.util.HashSet<>();
3696
+ if (fileExtensions != null && fileExtensions.length() > 0) {
3697
+ for (int i = 0; i < fileExtensions.length(); i++) {
3698
+ String ext = fileExtensions.getString(i);
3699
+ if (!ext.startsWith(".")) ext = "." + ext;
3700
+ allowedExtensions.add(ext.toLowerCase());
3701
+ }
3702
+ }
3703
+
3704
+ // 编译搜索模式
3705
+ java.util.regex.Pattern regex;
3706
+ int flags = caseSensitive ? 0 : java.util.regex.Pattern.CASE_INSENSITIVE;
3707
+ if (isRegex) {
3708
+ regex = java.util.regex.Pattern.compile(pattern, flags);
3709
+ } else {
3710
+ regex = java.util.regex.Pattern.compile(java.util.regex.Pattern.quote(pattern), flags);
3711
+ }
3712
+
3713
+ int totalFound = 0;
3714
+ int filesSearched = 0;
3715
+ boolean truncated = false;
3716
+
3717
+ java.util.zip.ZipFile zipFile = null;
3718
+ try {
3719
+ zipFile = new java.util.zip.ZipFile(apkPath);
3720
+ java.util.Enumeration<? extends java.util.zip.ZipEntry> entries = zipFile.entries();
3721
+
3722
+ while (entries.hasMoreElements() && totalFound < maxResults) {
3723
+ java.util.zip.ZipEntry entry = entries.nextElement();
3724
+ if (entry.isDirectory()) continue;
3725
+
3726
+ String name = entry.getName();
3727
+ String ext = "";
3728
+ int dotIndex = name.lastIndexOf('.');
3729
+ if (dotIndex > 0) {
3730
+ ext = name.substring(dotIndex).toLowerCase();
3731
+ }
3732
+
3733
+ // 跳过二进制文件
3734
+ if (binaryExtensions.contains(ext)) continue;
3735
+
3736
+ // 检查扩展名过滤
3737
+ if (!allowedExtensions.isEmpty() && !allowedExtensions.contains(ext)) continue;
3738
+
3739
+ // 跳过大文件(> 1MB)
3740
+ if (entry.getSize() > 1024 * 1024) continue;
3741
+
3742
+ try {
3743
+ java.io.InputStream is = zipFile.getInputStream(entry);
3744
+ byte[] data = new byte[(int) entry.getSize()];
3745
+ int totalRead = 0;
3746
+ int read;
3747
+ while (totalRead < data.length && (read = is.read(data, totalRead, data.length - totalRead)) != -1) {
3748
+ totalRead += read;
3749
+ }
3750
+ is.close();
3751
+
3752
+ // 检查是否是二进制
3753
+ boolean isBinary = false;
3754
+ for (int i = 0; i < Math.min(100, data.length); i++) {
3755
+ if (data[i] == 0) {
3756
+ isBinary = true;
3757
+ break;
3758
+ }
3759
+ }
3760
+ if (isBinary) continue;
3761
+
3762
+ String content = new String(data, java.nio.charset.StandardCharsets.UTF_8);
3763
+ String[] lines = content.split("\n");
3764
+
3765
+ filesSearched++;
3766
+
3767
+ for (int i = 0; i < lines.length && totalFound < maxResults; i++) {
3768
+ java.util.regex.Matcher matcher = regex.matcher(lines[i]);
3769
+ if (matcher.find()) {
3770
+ JSObject match = new JSObject();
3771
+ match.put("file", name);
3772
+ match.put("lineNumber", i + 1);
3773
+ match.put("line", lines[i].trim());
3774
+
3775
+ // 添加上下文
3776
+ JSArray context = new JSArray();
3777
+ int start = Math.max(0, i - contextLines);
3778
+ int end = Math.min(lines.length, i + contextLines + 1);
3779
+ for (int j = start; j < end; j++) {
3780
+ context.put(lines[j]);
3781
+ }
3782
+ match.put("context", context);
3783
+
3784
+ results.put(match);
3785
+ totalFound++;
3786
+ }
3787
+ }
3788
+ } catch (Exception e) {
3789
+ // 跳过无法读取的文件
3790
+ }
3791
+ }
3792
+
3793
+ truncated = totalFound >= maxResults;
3794
+
3795
+ } finally {
3796
+ if (zipFile != null) {
3797
+ try { zipFile.close(); } catch (Exception ignored) {}
3798
+ }
3799
+ }
3800
+
3801
+ result.put("results", results);
3802
+ result.put("totalFound", totalFound);
3803
+ result.put("filesSearched", filesSearched);
3804
+ result.put("truncated", truncated);
3805
+
3806
+ return result;
3807
+ }
3808
+
3679
3809
  /**
3680
3810
  * 清理临时目录
3681
3811
  */
@@ -105,4 +105,99 @@ public class RustDex {
105
105
  byte[] dexBytes,
106
106
  String className
107
107
  );
108
+
109
+ // ==================== 方法级操作 ====================
110
+
111
+ /**
112
+ * 列出类中的所有方法
113
+ * @param dexBytes DEX 文件字节数组
114
+ * @param className 类名
115
+ * @return JSON 格式的方法列表
116
+ */
117
+ public static native String listMethods(
118
+ byte[] dexBytes,
119
+ String className
120
+ );
121
+
122
+ /**
123
+ * 获取单个方法的 Smali 代码
124
+ * @param dexBytes DEX 文件字节数组
125
+ * @param className 类名
126
+ * @param methodName 方法名
127
+ * @param methodSignature 方法签名(可为空字符串表示不限制)
128
+ * @return JSON 格式的方法 Smali 代码
129
+ */
130
+ public static native String getMethod(
131
+ byte[] dexBytes,
132
+ String className,
133
+ String methodName,
134
+ String methodSignature
135
+ );
136
+
137
+ /**
138
+ * 添加方法到类
139
+ * @param dexBytes DEX 文件字节数组
140
+ * @param className 类名
141
+ * @param methodSmali 方法的 Smali 代码
142
+ * @return 修改后的 DEX 字节数组,失败返回 null
143
+ */
144
+ public static native byte[] addMethod(
145
+ byte[] dexBytes,
146
+ String className,
147
+ String methodSmali
148
+ );
149
+
150
+ /**
151
+ * 删除类中的方法
152
+ * @param dexBytes DEX 文件字节数组
153
+ * @param className 类名
154
+ * @param methodName 方法名
155
+ * @param methodSignature 方法签名(可为空字符串表示不限制)
156
+ * @return 修改后的 DEX 字节数组,失败返回 null
157
+ */
158
+ public static native byte[] deleteMethod(
159
+ byte[] dexBytes,
160
+ String className,
161
+ String methodName,
162
+ String methodSignature
163
+ );
164
+
165
+ // ==================== 字段级操作 ====================
166
+
167
+ /**
168
+ * 列出类中的所有字段
169
+ * @param dexBytes DEX 文件字节数组
170
+ * @param className 类名
171
+ * @return JSON 格式的字段列表
172
+ */
173
+ public static native String listFields(
174
+ byte[] dexBytes,
175
+ String className
176
+ );
177
+
178
+ /**
179
+ * 添加字段到类
180
+ * @param dexBytes DEX 文件字节数组
181
+ * @param className 类名
182
+ * @param fieldSmali 字段的 Smali 定义(如 ".field public myField:I")
183
+ * @return 修改后的 DEX 字节数组,失败返回 null
184
+ */
185
+ public static native byte[] addField(
186
+ byte[] dexBytes,
187
+ String className,
188
+ String fieldSmali
189
+ );
190
+
191
+ /**
192
+ * 删除类中的字段
193
+ * @param dexBytes DEX 文件字节数组
194
+ * @param className 类名
195
+ * @param fieldName 字段名
196
+ * @return 修改后的 DEX 字节数组,失败返回 null
197
+ */
198
+ public static native byte[] deleteField(
199
+ byte[] dexBytes,
200
+ String className,
201
+ String fieldName
202
+ );
108
203
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "capacitor-dex-editor",
3
- "version": "0.0.67",
3
+ "version": "0.0.69",
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",