capacitor-dex-editor 0.0.35 → 0.0.37
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/build.gradle
CHANGED
|
@@ -68,8 +68,7 @@ dependencies {
|
|
|
68
68
|
// APK Signer - V1/V2/V3/V4 签名支持 (Android 7.0+)
|
|
69
69
|
api 'com.android.tools.build:apksig:8.7.2'
|
|
70
70
|
|
|
71
|
-
// ARSCLib
|
|
72
|
-
api 'io.github.reandroid:ARSCLib:1.3.8'
|
|
71
|
+
// 移除 ARSCLib,使用内置 AXML 解析器
|
|
73
72
|
|
|
74
73
|
testImplementation "junit:junit:$junitVersion"
|
|
75
74
|
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
package com.aetherlink.dexeditor;
|
|
2
|
+
|
|
3
|
+
import java.io.ByteArrayOutputStream;
|
|
4
|
+
import java.nio.ByteBuffer;
|
|
5
|
+
import java.nio.ByteOrder;
|
|
6
|
+
import java.nio.charset.StandardCharsets;
|
|
7
|
+
import java.util.ArrayList;
|
|
8
|
+
import java.util.List;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* AXML 编辑器 - 支持任意长度的字符串替换
|
|
12
|
+
* 通过重建字符串池实现
|
|
13
|
+
*/
|
|
14
|
+
public class AxmlEditor {
|
|
15
|
+
|
|
16
|
+
private static final int AXML_MAGIC = 0x00080003;
|
|
17
|
+
private static final int STRING_POOL_TYPE = 0x0001;
|
|
18
|
+
|
|
19
|
+
private byte[] data;
|
|
20
|
+
private int stringPoolStart;
|
|
21
|
+
private int stringPoolSize;
|
|
22
|
+
private int stringCount;
|
|
23
|
+
private int styleCount;
|
|
24
|
+
private int flags;
|
|
25
|
+
private int stringsOffset;
|
|
26
|
+
private int stylesOffset;
|
|
27
|
+
private List<String> strings;
|
|
28
|
+
private byte[] beforeStringPool;
|
|
29
|
+
private byte[] afterStringPool;
|
|
30
|
+
private boolean isUtf8;
|
|
31
|
+
|
|
32
|
+
public AxmlEditor(byte[] axmlData) {
|
|
33
|
+
this.data = axmlData;
|
|
34
|
+
this.strings = new ArrayList<>();
|
|
35
|
+
parse();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private void parse() {
|
|
39
|
+
if (data.length < 8) return;
|
|
40
|
+
|
|
41
|
+
ByteBuffer buffer = ByteBuffer.wrap(data);
|
|
42
|
+
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
|
43
|
+
|
|
44
|
+
// 检查魔数
|
|
45
|
+
int magic = buffer.getInt();
|
|
46
|
+
if (magic != AXML_MAGIC) return;
|
|
47
|
+
|
|
48
|
+
int fileSize = buffer.getInt();
|
|
49
|
+
|
|
50
|
+
// 查找字符串池
|
|
51
|
+
while (buffer.position() < data.length - 8) {
|
|
52
|
+
int chunkStart = buffer.position();
|
|
53
|
+
int chunkType = buffer.getShort() & 0xFFFF;
|
|
54
|
+
int headerSize = buffer.getShort() & 0xFFFF;
|
|
55
|
+
int chunkSize = buffer.getInt();
|
|
56
|
+
|
|
57
|
+
if (chunkType == STRING_POOL_TYPE) {
|
|
58
|
+
stringPoolStart = chunkStart;
|
|
59
|
+
stringPoolSize = chunkSize;
|
|
60
|
+
|
|
61
|
+
// 保存字符串池之前的数据
|
|
62
|
+
beforeStringPool = new byte[chunkStart];
|
|
63
|
+
System.arraycopy(data, 0, beforeStringPool, 0, chunkStart);
|
|
64
|
+
|
|
65
|
+
// 保存字符串池之后的数据
|
|
66
|
+
int afterStart = chunkStart + chunkSize;
|
|
67
|
+
if (afterStart < data.length) {
|
|
68
|
+
afterStringPool = new byte[data.length - afterStart];
|
|
69
|
+
System.arraycopy(data, afterStart, afterStringPool, 0, afterStringPool.length);
|
|
70
|
+
} else {
|
|
71
|
+
afterStringPool = new byte[0];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 解析字符串池
|
|
75
|
+
stringCount = buffer.getInt();
|
|
76
|
+
styleCount = buffer.getInt();
|
|
77
|
+
flags = buffer.getInt();
|
|
78
|
+
stringsOffset = buffer.getInt();
|
|
79
|
+
stylesOffset = buffer.getInt();
|
|
80
|
+
|
|
81
|
+
isUtf8 = (flags & 0x100) != 0;
|
|
82
|
+
|
|
83
|
+
// 读取字符串偏移表
|
|
84
|
+
int[] offsets = new int[stringCount];
|
|
85
|
+
for (int i = 0; i < stringCount; i++) {
|
|
86
|
+
offsets[i] = buffer.getInt();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 读取字符串
|
|
90
|
+
int stringsStart = chunkStart + stringsOffset;
|
|
91
|
+
for (int i = 0; i < stringCount; i++) {
|
|
92
|
+
int stringStart = stringsStart + offsets[i];
|
|
93
|
+
if (stringStart < data.length) {
|
|
94
|
+
strings.add(readStringAt(stringStart));
|
|
95
|
+
} else {
|
|
96
|
+
strings.add("");
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
buffer.position(chunkStart + chunkSize);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private String readStringAt(int pos) {
|
|
108
|
+
try {
|
|
109
|
+
if (isUtf8) {
|
|
110
|
+
int charLen = data[pos] & 0xFF;
|
|
111
|
+
int byteLen;
|
|
112
|
+
int dataStart;
|
|
113
|
+
|
|
114
|
+
if ((charLen & 0x80) != 0) {
|
|
115
|
+
charLen = ((charLen & 0x7F) << 8) | (data[pos + 1] & 0xFF);
|
|
116
|
+
byteLen = data[pos + 2] & 0xFF;
|
|
117
|
+
if ((byteLen & 0x80) != 0) {
|
|
118
|
+
byteLen = ((byteLen & 0x7F) << 8) | (data[pos + 3] & 0xFF);
|
|
119
|
+
dataStart = pos + 4;
|
|
120
|
+
} else {
|
|
121
|
+
dataStart = pos + 3;
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
byteLen = data[pos + 1] & 0xFF;
|
|
125
|
+
if ((byteLen & 0x80) != 0) {
|
|
126
|
+
byteLen = ((byteLen & 0x7F) << 8) | (data[pos + 2] & 0xFF);
|
|
127
|
+
dataStart = pos + 3;
|
|
128
|
+
} else {
|
|
129
|
+
dataStart = pos + 2;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (dataStart + byteLen > data.length) {
|
|
134
|
+
byteLen = data.length - dataStart;
|
|
135
|
+
}
|
|
136
|
+
if (byteLen <= 0) return "";
|
|
137
|
+
|
|
138
|
+
return new String(data, dataStart, byteLen, StandardCharsets.UTF_8);
|
|
139
|
+
} else {
|
|
140
|
+
int charLen = (data[pos] & 0xFF) | ((data[pos + 1] & 0xFF) << 8);
|
|
141
|
+
if ((charLen & 0x8000) != 0) {
|
|
142
|
+
int high = (data[pos + 2] & 0xFF) | ((data[pos + 3] & 0xFF) << 8);
|
|
143
|
+
charLen = ((charLen & 0x7FFF) << 16) | high;
|
|
144
|
+
pos += 4;
|
|
145
|
+
} else {
|
|
146
|
+
pos += 2;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (pos + charLen * 2 > data.length) {
|
|
150
|
+
charLen = (data.length - pos) / 2;
|
|
151
|
+
}
|
|
152
|
+
if (charLen <= 0) return "";
|
|
153
|
+
|
|
154
|
+
char[] chars = new char[charLen];
|
|
155
|
+
for (int i = 0; i < charLen; i++) {
|
|
156
|
+
chars[i] = (char) ((data[pos + i * 2] & 0xFF) | ((data[pos + i * 2 + 1] & 0xFF) << 8));
|
|
157
|
+
}
|
|
158
|
+
return new String(chars);
|
|
159
|
+
}
|
|
160
|
+
} catch (Exception e) {
|
|
161
|
+
return "";
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* 替换字符串
|
|
167
|
+
* @return 替换的次数
|
|
168
|
+
*/
|
|
169
|
+
public int replaceString(String oldValue, String newValue) {
|
|
170
|
+
int count = 0;
|
|
171
|
+
for (int i = 0; i < strings.size(); i++) {
|
|
172
|
+
String str = strings.get(i);
|
|
173
|
+
if (str != null && str.contains(oldValue)) {
|
|
174
|
+
strings.set(i, str.replace(oldValue, newValue));
|
|
175
|
+
count++;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return count;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* 构建修改后的 AXML 数据
|
|
183
|
+
*/
|
|
184
|
+
public byte[] build() {
|
|
185
|
+
if (strings.isEmpty() || beforeStringPool == null) {
|
|
186
|
+
return data;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
191
|
+
|
|
192
|
+
// 写入字符串池之前的数据
|
|
193
|
+
baos.write(beforeStringPool);
|
|
194
|
+
|
|
195
|
+
// 构建新的字符串池
|
|
196
|
+
byte[] newStringPool = buildStringPool();
|
|
197
|
+
baos.write(newStringPool);
|
|
198
|
+
|
|
199
|
+
// 写入字符串池之后的数据
|
|
200
|
+
baos.write(afterStringPool);
|
|
201
|
+
|
|
202
|
+
// 更新文件大小
|
|
203
|
+
byte[] result = baos.toByteArray();
|
|
204
|
+
int newFileSize = result.length;
|
|
205
|
+
|
|
206
|
+
// 更新文件头中的大小
|
|
207
|
+
result[4] = (byte) (newFileSize & 0xFF);
|
|
208
|
+
result[5] = (byte) ((newFileSize >> 8) & 0xFF);
|
|
209
|
+
result[6] = (byte) ((newFileSize >> 16) & 0xFF);
|
|
210
|
+
result[7] = (byte) ((newFileSize >> 24) & 0xFF);
|
|
211
|
+
|
|
212
|
+
return result;
|
|
213
|
+
} catch (Exception e) {
|
|
214
|
+
return data;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private byte[] buildStringPool() throws Exception {
|
|
219
|
+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
220
|
+
|
|
221
|
+
// 先构建字符串数据
|
|
222
|
+
ByteArrayOutputStream stringData = new ByteArrayOutputStream();
|
|
223
|
+
int[] offsets = new int[strings.size()];
|
|
224
|
+
|
|
225
|
+
for (int i = 0; i < strings.size(); i++) {
|
|
226
|
+
offsets[i] = stringData.size();
|
|
227
|
+
String str = strings.get(i);
|
|
228
|
+
if (str == null) str = "";
|
|
229
|
+
|
|
230
|
+
if (isUtf8) {
|
|
231
|
+
byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
|
|
232
|
+
int charLen = str.length();
|
|
233
|
+
int byteLen = bytes.length;
|
|
234
|
+
|
|
235
|
+
// 写入字符长度
|
|
236
|
+
if (charLen > 127) {
|
|
237
|
+
stringData.write((charLen >> 8) | 0x80);
|
|
238
|
+
stringData.write(charLen & 0xFF);
|
|
239
|
+
} else {
|
|
240
|
+
stringData.write(charLen);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// 写入字节长度
|
|
244
|
+
if (byteLen > 127) {
|
|
245
|
+
stringData.write((byteLen >> 8) | 0x80);
|
|
246
|
+
stringData.write(byteLen & 0xFF);
|
|
247
|
+
} else {
|
|
248
|
+
stringData.write(byteLen);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// 写入字符串数据
|
|
252
|
+
stringData.write(bytes);
|
|
253
|
+
stringData.write(0); // null terminator
|
|
254
|
+
} else {
|
|
255
|
+
// UTF-16
|
|
256
|
+
int charLen = str.length();
|
|
257
|
+
if (charLen > 0x7FFF) {
|
|
258
|
+
stringData.write((charLen & 0xFF) | 0x80);
|
|
259
|
+
stringData.write((charLen >> 8) | 0x80);
|
|
260
|
+
stringData.write((charLen >> 16) & 0xFF);
|
|
261
|
+
stringData.write((charLen >> 24) & 0xFF);
|
|
262
|
+
} else {
|
|
263
|
+
stringData.write(charLen & 0xFF);
|
|
264
|
+
stringData.write((charLen >> 8) & 0xFF);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
for (int j = 0; j < str.length(); j++) {
|
|
268
|
+
char c = str.charAt(j);
|
|
269
|
+
stringData.write(c & 0xFF);
|
|
270
|
+
stringData.write((c >> 8) & 0xFF);
|
|
271
|
+
}
|
|
272
|
+
// null terminator
|
|
273
|
+
stringData.write(0);
|
|
274
|
+
stringData.write(0);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// 计算头部大小
|
|
279
|
+
int headerSize = 28; // 固定头部
|
|
280
|
+
int offsetTableSize = strings.size() * 4;
|
|
281
|
+
int styleOffsetTableSize = styleCount * 4;
|
|
282
|
+
int stringsDataSize = stringData.size();
|
|
283
|
+
|
|
284
|
+
// 对齐到 4 字节
|
|
285
|
+
int padding = (4 - (stringsDataSize % 4)) % 4;
|
|
286
|
+
stringsDataSize += padding;
|
|
287
|
+
|
|
288
|
+
int newStringsOffset = headerSize + offsetTableSize + styleOffsetTableSize;
|
|
289
|
+
int totalSize = newStringsOffset + stringsDataSize;
|
|
290
|
+
|
|
291
|
+
// 写入 chunk 头
|
|
292
|
+
ByteBuffer header = ByteBuffer.allocate(headerSize);
|
|
293
|
+
header.order(ByteOrder.LITTLE_ENDIAN);
|
|
294
|
+
header.putShort((short) STRING_POOL_TYPE); // type
|
|
295
|
+
header.putShort((short) headerSize); // header size
|
|
296
|
+
header.putInt(totalSize); // chunk size
|
|
297
|
+
header.putInt(strings.size()); // string count
|
|
298
|
+
header.putInt(styleCount); // style count
|
|
299
|
+
header.putInt(flags); // flags
|
|
300
|
+
header.putInt(newStringsOffset); // strings offset
|
|
301
|
+
header.putInt(0); // styles offset (暂不支持)
|
|
302
|
+
|
|
303
|
+
baos.write(header.array());
|
|
304
|
+
|
|
305
|
+
// 写入偏移表
|
|
306
|
+
for (int offset : offsets) {
|
|
307
|
+
baos.write(offset & 0xFF);
|
|
308
|
+
baos.write((offset >> 8) & 0xFF);
|
|
309
|
+
baos.write((offset >> 16) & 0xFF);
|
|
310
|
+
baos.write((offset >> 24) & 0xFF);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// 写入样式偏移表(空)
|
|
314
|
+
for (int i = 0; i < styleCount; i++) {
|
|
315
|
+
baos.write(0);
|
|
316
|
+
baos.write(0);
|
|
317
|
+
baos.write(0);
|
|
318
|
+
baos.write(0);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// 写入字符串数据
|
|
322
|
+
baos.write(stringData.toByteArray());
|
|
323
|
+
|
|
324
|
+
// 写入对齐填充
|
|
325
|
+
for (int i = 0; i < padding; i++) {
|
|
326
|
+
baos.write(0);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return baos.toByteArray();
|
|
330
|
+
}
|
|
331
|
+
}
|
|
@@ -2407,31 +2407,39 @@ public class DexManager {
|
|
|
2407
2407
|
// ==================== XML/资源操作方法 ====================
|
|
2408
2408
|
|
|
2409
2409
|
/**
|
|
2410
|
-
* 获取 APK 的 AndroidManifest.xml
|
|
2410
|
+
* 获取 APK 的 AndroidManifest.xml(解码为可读 XML)
|
|
2411
2411
|
*/
|
|
2412
2412
|
public JSObject getManifestFromApk(String apkPath) throws Exception {
|
|
2413
2413
|
JSObject result = new JSObject();
|
|
2414
2414
|
|
|
2415
|
+
java.util.zip.ZipFile zipFile = null;
|
|
2415
2416
|
try {
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
com.reandroid.arsc.chunk.xml.AndroidManifestBlock manifest = apkModule.getAndroidManifest();
|
|
2417
|
+
zipFile = new java.util.zip.ZipFile(apkPath);
|
|
2418
|
+
java.util.zip.ZipEntry manifestEntry = zipFile.getEntry("AndroidManifest.xml");
|
|
2419
2419
|
|
|
2420
|
-
if (
|
|
2421
|
-
apkModule.close();
|
|
2420
|
+
if (manifestEntry == null) {
|
|
2422
2421
|
throw new Exception("AndroidManifest.xml not found in APK");
|
|
2423
2422
|
}
|
|
2424
2423
|
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2424
|
+
java.io.InputStream is = zipFile.getInputStream(manifestEntry);
|
|
2425
|
+
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
|
2426
|
+
byte[] buffer = new byte[8192];
|
|
2427
|
+
int len;
|
|
2428
|
+
while ((len = is.read(buffer)) != -1) {
|
|
2429
|
+
baos.write(buffer, 0, len);
|
|
2430
|
+
}
|
|
2431
|
+
is.close();
|
|
2432
|
+
|
|
2433
|
+
// 解析二进制 AXML
|
|
2434
|
+
byte[] axmlData = baos.toByteArray();
|
|
2435
|
+
String xmlContent = decodeAxml(axmlData);
|
|
2428
2436
|
|
|
2429
2437
|
result.put("manifest", xmlContent);
|
|
2430
2438
|
|
|
2431
|
-
}
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2439
|
+
} finally {
|
|
2440
|
+
if (zipFile != null) {
|
|
2441
|
+
try { zipFile.close(); } catch (Exception ignored) {}
|
|
2442
|
+
}
|
|
2435
2443
|
}
|
|
2436
2444
|
|
|
2437
2445
|
return result;
|
|
@@ -2661,7 +2669,7 @@ public class DexManager {
|
|
|
2661
2669
|
}
|
|
2662
2670
|
|
|
2663
2671
|
/**
|
|
2664
|
-
* 精准替换 AndroidManifest.xml
|
|
2672
|
+
* 精准替换 AndroidManifest.xml 中的字符串(二进制替换,支持任意长度)
|
|
2665
2673
|
*/
|
|
2666
2674
|
public JSObject replaceInManifest(String apkPath, org.json.JSONArray replacements) throws Exception {
|
|
2667
2675
|
JSObject result = new JSObject();
|
|
@@ -2669,34 +2677,37 @@ public class DexManager {
|
|
|
2669
2677
|
int replacedCount = 0;
|
|
2670
2678
|
|
|
2671
2679
|
try {
|
|
2672
|
-
//
|
|
2673
|
-
|
|
2674
|
-
|
|
2680
|
+
// 读取 APK 中的 AndroidManifest.xml
|
|
2681
|
+
java.util.zip.ZipFile zipFile = new java.util.zip.ZipFile(apkPath);
|
|
2682
|
+
java.util.zip.ZipEntry manifestEntry = zipFile.getEntry("AndroidManifest.xml");
|
|
2675
2683
|
|
|
2676
|
-
if (
|
|
2684
|
+
if (manifestEntry == null) {
|
|
2685
|
+
zipFile.close();
|
|
2677
2686
|
throw new Exception("AndroidManifest.xml not found in APK");
|
|
2678
2687
|
}
|
|
2679
2688
|
|
|
2680
|
-
//
|
|
2681
|
-
|
|
2682
|
-
|
|
2689
|
+
// 读取 AXML 数据
|
|
2690
|
+
java.io.InputStream is = zipFile.getInputStream(manifestEntry);
|
|
2691
|
+
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
|
2692
|
+
byte[] buffer = new byte[8192];
|
|
2693
|
+
int len;
|
|
2694
|
+
while ((len = is.read(buffer)) != -1) {
|
|
2695
|
+
baos.write(buffer, 0, len);
|
|
2696
|
+
}
|
|
2697
|
+
is.close();
|
|
2698
|
+
zipFile.close();
|
|
2699
|
+
|
|
2700
|
+
byte[] axmlData = baos.toByteArray();
|
|
2701
|
+
|
|
2702
|
+
// 使用 AxmlEditor 执行替换(支持任意长度)
|
|
2703
|
+
AxmlEditor editor = new AxmlEditor(axmlData);
|
|
2683
2704
|
|
|
2684
|
-
// 执行替换
|
|
2685
2705
|
for (int i = 0; i < replacements.length(); i++) {
|
|
2686
2706
|
org.json.JSONObject replacement = replacements.getJSONObject(i);
|
|
2687
2707
|
String oldValue = replacement.getString("oldValue");
|
|
2688
2708
|
String newValue = replacement.getString("newValue");
|
|
2689
2709
|
|
|
2690
|
-
int count =
|
|
2691
|
-
String before = xmlContent;
|
|
2692
|
-
xmlContent = xmlContent.replace(oldValue, newValue);
|
|
2693
|
-
|
|
2694
|
-
// 计算替换次数
|
|
2695
|
-
int idx = 0;
|
|
2696
|
-
while ((idx = before.indexOf(oldValue, idx)) != -1) {
|
|
2697
|
-
count++;
|
|
2698
|
-
idx += oldValue.length();
|
|
2699
|
-
}
|
|
2710
|
+
int count = editor.replaceString(oldValue, newValue);
|
|
2700
2711
|
|
|
2701
2712
|
JSObject detail = new JSObject();
|
|
2702
2713
|
detail.put("oldValue", oldValue);
|
|
@@ -2708,7 +2719,6 @@ public class DexManager {
|
|
|
2708
2719
|
}
|
|
2709
2720
|
|
|
2710
2721
|
if (replacedCount == 0) {
|
|
2711
|
-
apkModule.close();
|
|
2712
2722
|
result.put("success", true);
|
|
2713
2723
|
result.put("replacedCount", 0);
|
|
2714
2724
|
result.put("details", details);
|
|
@@ -2716,17 +2726,55 @@ public class DexManager {
|
|
|
2716
2726
|
return result;
|
|
2717
2727
|
}
|
|
2718
2728
|
|
|
2719
|
-
//
|
|
2720
|
-
|
|
2721
|
-
manifest.refresh();
|
|
2729
|
+
// 获取修改后的数据
|
|
2730
|
+
byte[] modifiedData = editor.build();
|
|
2722
2731
|
|
|
2723
|
-
//
|
|
2732
|
+
// 替换 APK 中的 AndroidManifest.xml
|
|
2733
|
+
java.io.File apkFile = new java.io.File(apkPath);
|
|
2724
2734
|
java.io.File tempApk = new java.io.File(apkPath + ".tmp");
|
|
2725
|
-
|
|
2726
|
-
|
|
2735
|
+
|
|
2736
|
+
java.util.zip.ZipInputStream zis = new java.util.zip.ZipInputStream(
|
|
2737
|
+
new java.io.BufferedInputStream(new java.io.FileInputStream(apkFile)));
|
|
2738
|
+
java.util.zip.ZipOutputStream zos = new java.util.zip.ZipOutputStream(
|
|
2739
|
+
new java.io.BufferedOutputStream(new java.io.FileOutputStream(tempApk)));
|
|
2740
|
+
|
|
2741
|
+
java.util.zip.ZipEntry entry;
|
|
2742
|
+
while ((entry = zis.getNextEntry()) != null) {
|
|
2743
|
+
if (entry.getName().equals("AndroidManifest.xml")) {
|
|
2744
|
+
// 写入修改后的 Manifest
|
|
2745
|
+
java.util.zip.ZipEntry newEntry = new java.util.zip.ZipEntry("AndroidManifest.xml");
|
|
2746
|
+
newEntry.setMethod(java.util.zip.ZipEntry.DEFLATED);
|
|
2747
|
+
zos.putNextEntry(newEntry);
|
|
2748
|
+
zos.write(modifiedData);
|
|
2749
|
+
zos.closeEntry();
|
|
2750
|
+
} else {
|
|
2751
|
+
// 复制其他文件
|
|
2752
|
+
java.util.zip.ZipEntry newEntry = new java.util.zip.ZipEntry(entry.getName());
|
|
2753
|
+
newEntry.setTime(entry.getTime());
|
|
2754
|
+
if (entry.getMethod() == java.util.zip.ZipEntry.STORED) {
|
|
2755
|
+
newEntry.setMethod(java.util.zip.ZipEntry.STORED);
|
|
2756
|
+
newEntry.setSize(entry.getSize());
|
|
2757
|
+
newEntry.setCrc(entry.getCrc());
|
|
2758
|
+
} else {
|
|
2759
|
+
newEntry.setMethod(java.util.zip.ZipEntry.DEFLATED);
|
|
2760
|
+
}
|
|
2761
|
+
zos.putNextEntry(newEntry);
|
|
2762
|
+
if (!entry.isDirectory()) {
|
|
2763
|
+
byte[] buf = new byte[8192];
|
|
2764
|
+
int n;
|
|
2765
|
+
while ((n = zis.read(buf)) != -1) {
|
|
2766
|
+
zos.write(buf, 0, n);
|
|
2767
|
+
}
|
|
2768
|
+
}
|
|
2769
|
+
zos.closeEntry();
|
|
2770
|
+
}
|
|
2771
|
+
zis.closeEntry();
|
|
2772
|
+
}
|
|
2773
|
+
|
|
2774
|
+
zis.close();
|
|
2775
|
+
zos.close();
|
|
2727
2776
|
|
|
2728
2777
|
// 替换原文件
|
|
2729
|
-
java.io.File apkFile = new java.io.File(apkPath);
|
|
2730
2778
|
if (!apkFile.delete()) {
|
|
2731
2779
|
Log.e(TAG, "Failed to delete original APK");
|
|
2732
2780
|
}
|