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.
- package/android/src/main/java/com/aetherlink/dexeditor/DexEditorPluginPlugin.java +12 -0
- package/android/src/main/java/com/aetherlink/dexeditor/DexManager.java +130 -0
- package/android/src/main/java/com/aetherlink/dexeditor/RustDex.java +95 -0
- package/android/src/main/jniLibs/arm64-v8a/libdex_rust.so +0 -0
- package/android/src/main/jniLibs/armeabi-v7a/libdex_rust.so +0 -0
- package/android/src/main/jniLibs/x86/libdex_rust.so +0 -0
- package/android/src/main/jniLibs/x86_64/libdex_rust.so +0 -0
- package/package.json +1 -1
|
@@ -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
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|