capacitor-dex-editor 0.0.37 → 0.0.38
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.
|
@@ -538,6 +538,25 @@ public class DexEditorPluginPlugin extends Plugin {
|
|
|
538
538
|
));
|
|
539
539
|
break;
|
|
540
540
|
|
|
541
|
+
case "listApkFiles":
|
|
542
|
+
result.put("data", dexManager.listApkFiles(
|
|
543
|
+
params.getString("apkPath"),
|
|
544
|
+
params.optString("filter", ""),
|
|
545
|
+
params.optInt("limit", 100),
|
|
546
|
+
params.optInt("offset", 0)
|
|
547
|
+
));
|
|
548
|
+
break;
|
|
549
|
+
|
|
550
|
+
case "readApkFile":
|
|
551
|
+
result.put("data", dexManager.readApkFile(
|
|
552
|
+
params.getString("apkPath"),
|
|
553
|
+
params.getString("filePath"),
|
|
554
|
+
params.optBoolean("asBase64", false),
|
|
555
|
+
params.optInt("maxBytes", 0),
|
|
556
|
+
params.optInt("offset", 0)
|
|
557
|
+
));
|
|
558
|
+
break;
|
|
559
|
+
|
|
541
560
|
default:
|
|
542
561
|
result.put("success", false);
|
|
543
562
|
result.put("error", "Unknown action: " + action);
|
|
@@ -2973,6 +2973,171 @@ public class DexManager {
|
|
|
2973
2973
|
return pos + 2;
|
|
2974
2974
|
}
|
|
2975
2975
|
|
|
2976
|
+
/**
|
|
2977
|
+
* 列出 APK 中的所有文件
|
|
2978
|
+
*/
|
|
2979
|
+
public JSObject listApkFiles(String apkPath, String filter, int limit, int offset) throws Exception {
|
|
2980
|
+
JSObject result = new JSObject();
|
|
2981
|
+
JSArray files = new JSArray();
|
|
2982
|
+
|
|
2983
|
+
java.util.zip.ZipFile zipFile = null;
|
|
2984
|
+
try {
|
|
2985
|
+
zipFile = new java.util.zip.ZipFile(apkPath);
|
|
2986
|
+
java.util.Enumeration<? extends java.util.zip.ZipEntry> entries = zipFile.entries();
|
|
2987
|
+
|
|
2988
|
+
java.util.List<JSObject> allFiles = new java.util.ArrayList<>();
|
|
2989
|
+
|
|
2990
|
+
while (entries.hasMoreElements()) {
|
|
2991
|
+
java.util.zip.ZipEntry entry = entries.nextElement();
|
|
2992
|
+
String name = entry.getName();
|
|
2993
|
+
|
|
2994
|
+
// 应用过滤
|
|
2995
|
+
if (!filter.isEmpty() && !name.contains(filter)) {
|
|
2996
|
+
continue;
|
|
2997
|
+
}
|
|
2998
|
+
|
|
2999
|
+
JSObject fileInfo = new JSObject();
|
|
3000
|
+
fileInfo.put("path", name);
|
|
3001
|
+
fileInfo.put("size", entry.getSize());
|
|
3002
|
+
fileInfo.put("compressedSize", entry.getCompressedSize());
|
|
3003
|
+
fileInfo.put("isDirectory", entry.isDirectory());
|
|
3004
|
+
|
|
3005
|
+
// 判断文件类型
|
|
3006
|
+
String type = "unknown";
|
|
3007
|
+
if (name.endsWith(".dex")) type = "dex";
|
|
3008
|
+
else if (name.endsWith(".so")) type = "native";
|
|
3009
|
+
else if (name.endsWith(".xml")) type = "xml";
|
|
3010
|
+
else if (name.startsWith("res/")) type = "resource";
|
|
3011
|
+
else if (name.startsWith("assets/")) type = "asset";
|
|
3012
|
+
else if (name.startsWith("lib/")) type = "native";
|
|
3013
|
+
else if (name.startsWith("META-INF/")) type = "meta";
|
|
3014
|
+
else if (name.equals("AndroidManifest.xml")) type = "manifest";
|
|
3015
|
+
else if (name.equals("resources.arsc")) type = "arsc";
|
|
3016
|
+
fileInfo.put("type", type);
|
|
3017
|
+
|
|
3018
|
+
allFiles.add(fileInfo);
|
|
3019
|
+
}
|
|
3020
|
+
|
|
3021
|
+
int total = allFiles.size();
|
|
3022
|
+
|
|
3023
|
+
// 分页
|
|
3024
|
+
int start = Math.min(offset, total);
|
|
3025
|
+
int end = Math.min(offset + limit, total);
|
|
3026
|
+
|
|
3027
|
+
for (int i = start; i < end; i++) {
|
|
3028
|
+
files.put(allFiles.get(i));
|
|
3029
|
+
}
|
|
3030
|
+
|
|
3031
|
+
result.put("files", files);
|
|
3032
|
+
result.put("total", total);
|
|
3033
|
+
result.put("offset", offset);
|
|
3034
|
+
result.put("limit", limit);
|
|
3035
|
+
result.put("returned", files.length());
|
|
3036
|
+
result.put("hasMore", end < total);
|
|
3037
|
+
|
|
3038
|
+
} finally {
|
|
3039
|
+
if (zipFile != null) {
|
|
3040
|
+
try { zipFile.close(); } catch (Exception ignored) {}
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
3043
|
+
|
|
3044
|
+
return result;
|
|
3045
|
+
}
|
|
3046
|
+
|
|
3047
|
+
/**
|
|
3048
|
+
* 读取 APK 中的任意文件
|
|
3049
|
+
*/
|
|
3050
|
+
public JSObject readApkFile(String apkPath, String filePath, boolean asBase64, int maxBytes, int offset) throws Exception {
|
|
3051
|
+
JSObject result = new JSObject();
|
|
3052
|
+
|
|
3053
|
+
java.util.zip.ZipFile zipFile = null;
|
|
3054
|
+
try {
|
|
3055
|
+
zipFile = new java.util.zip.ZipFile(apkPath);
|
|
3056
|
+
java.util.zip.ZipEntry entry = zipFile.getEntry(filePath);
|
|
3057
|
+
|
|
3058
|
+
if (entry == null) {
|
|
3059
|
+
result.put("error", "File not found: " + filePath);
|
|
3060
|
+
return result;
|
|
3061
|
+
}
|
|
3062
|
+
|
|
3063
|
+
result.put("path", filePath);
|
|
3064
|
+
result.put("size", entry.getSize());
|
|
3065
|
+
result.put("compressedSize", entry.getCompressedSize());
|
|
3066
|
+
|
|
3067
|
+
java.io.InputStream is = zipFile.getInputStream(entry);
|
|
3068
|
+
|
|
3069
|
+
// 跳过偏移量
|
|
3070
|
+
if (offset > 0) {
|
|
3071
|
+
is.skip(offset);
|
|
3072
|
+
}
|
|
3073
|
+
|
|
3074
|
+
// 读取数据
|
|
3075
|
+
int readSize = maxBytes > 0 ? maxBytes : (int) entry.getSize();
|
|
3076
|
+
if (readSize > 1024 * 1024) { // 限制最大 1MB
|
|
3077
|
+
readSize = 1024 * 1024;
|
|
3078
|
+
}
|
|
3079
|
+
|
|
3080
|
+
byte[] buffer = new byte[readSize];
|
|
3081
|
+
int totalRead = 0;
|
|
3082
|
+
int read;
|
|
3083
|
+
while (totalRead < readSize && (read = is.read(buffer, totalRead, readSize - totalRead)) != -1) {
|
|
3084
|
+
totalRead += read;
|
|
3085
|
+
}
|
|
3086
|
+
is.close();
|
|
3087
|
+
|
|
3088
|
+
byte[] data = new byte[totalRead];
|
|
3089
|
+
System.arraycopy(buffer, 0, data, 0, totalRead);
|
|
3090
|
+
|
|
3091
|
+
if (asBase64) {
|
|
3092
|
+
// Base64 编码返回
|
|
3093
|
+
result.put("content", android.util.Base64.encodeToString(data, android.util.Base64.NO_WRAP));
|
|
3094
|
+
result.put("encoding", "base64");
|
|
3095
|
+
} else {
|
|
3096
|
+
// 尝试作为文本返回
|
|
3097
|
+
String content = new String(data, java.nio.charset.StandardCharsets.UTF_8);
|
|
3098
|
+
|
|
3099
|
+
// 检查是否是二进制文件
|
|
3100
|
+
boolean isBinary = false;
|
|
3101
|
+
for (int i = 0; i < Math.min(100, data.length); i++) {
|
|
3102
|
+
if (data[i] == 0) {
|
|
3103
|
+
isBinary = true;
|
|
3104
|
+
break;
|
|
3105
|
+
}
|
|
3106
|
+
}
|
|
3107
|
+
|
|
3108
|
+
if (isBinary && !filePath.endsWith(".xml")) {
|
|
3109
|
+
// 二进制文件自动使用 Base64
|
|
3110
|
+
result.put("content", android.util.Base64.encodeToString(data, android.util.Base64.NO_WRAP));
|
|
3111
|
+
result.put("encoding", "base64");
|
|
3112
|
+
result.put("note", "Binary file, auto-encoded as base64");
|
|
3113
|
+
} else {
|
|
3114
|
+
// 如果是 XML 文件,尝试解码 AXML
|
|
3115
|
+
if (filePath.endsWith(".xml") && data.length > 4) {
|
|
3116
|
+
int magic = (data[0] & 0xFF) | ((data[1] & 0xFF) << 8) |
|
|
3117
|
+
((data[2] & 0xFF) << 16) | ((data[3] & 0xFF) << 24);
|
|
3118
|
+
if (magic == 0x00080003) {
|
|
3119
|
+
// 是 AXML 格式,解码
|
|
3120
|
+
content = AxmlParser.decode(data);
|
|
3121
|
+
}
|
|
3122
|
+
}
|
|
3123
|
+
result.put("content", content);
|
|
3124
|
+
result.put("encoding", "text");
|
|
3125
|
+
}
|
|
3126
|
+
}
|
|
3127
|
+
|
|
3128
|
+
result.put("offset", offset);
|
|
3129
|
+
result.put("bytesRead", totalRead);
|
|
3130
|
+
result.put("hasMore", offset + totalRead < entry.getSize());
|
|
3131
|
+
|
|
3132
|
+
} finally {
|
|
3133
|
+
if (zipFile != null) {
|
|
3134
|
+
try { zipFile.close(); } catch (Exception ignored) {}
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
|
|
3138
|
+
return result;
|
|
3139
|
+
}
|
|
3140
|
+
|
|
2976
3141
|
/**
|
|
2977
3142
|
* 清理临时目录
|
|
2978
3143
|
*/
|