capacitor-dex-editor 0.0.29 → 0.0.31

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,289 +1,298 @@
1
1
  package com.aetherlink.dexeditor;
2
2
 
3
- import java.io.ByteArrayInputStream;
4
- import java.nio.ByteBuffer;
5
- import java.nio.ByteOrder;
3
+ import android.util.Log;
6
4
 
7
5
  /**
8
6
  * 简单的 AXML (Android Binary XML) 解析器
9
7
  * 用于解码 APK 中的二进制 XML 文件
10
8
  */
11
9
  public class AxmlParser {
12
-
10
+
11
+ private static final String TAG = "AxmlParser";
12
+
13
13
  // AXML 文件头魔数
14
14
  private static final int AXML_MAGIC = 0x00080003;
15
-
16
- // Chunk 类型
17
- private static final int CHUNK_STRING_POOL = 0x001C0001;
18
- private static final int CHUNK_RESOURCE_IDS = 0x00080180;
19
- private static final int CHUNK_START_NAMESPACE = 0x00100100;
20
- private static final int CHUNK_END_NAMESPACE = 0x00100101;
21
- private static final int CHUNK_START_TAG = 0x00100102;
22
- private static final int CHUNK_END_TAG = 0x00100103;
23
- private static final int CHUNK_TEXT = 0x00100104;
24
-
25
- private ByteBuffer buffer;
15
+ private static final int STRING_POOL_TYPE = 0x0001;
16
+ private static final int XML_START_TAG = 0x0102;
17
+ private static final int XML_END_TAG = 0x0103;
18
+ private static final int XML_START_NAMESPACE = 0x0100;
19
+ private static final int XML_END_NAMESPACE = 0x0101;
20
+
21
+ private byte[] data;
22
+ private int position;
26
23
  private String[] stringPool;
27
24
  private StringBuilder xml;
28
25
  private int indent = 0;
29
-
26
+
30
27
  /**
31
28
  * 解码 AXML 数据为可读 XML 字符串
32
29
  */
33
- public static String decode(byte[] data) throws Exception {
34
- AxmlParser parser = new AxmlParser();
35
- return parser.parse(data);
30
+ public static String decode(byte[] data) {
31
+ try {
32
+ AxmlParser parser = new AxmlParser();
33
+ return parser.parse(data);
34
+ } catch (Exception e) {
35
+ Log.e(TAG, "AXML parse error", e);
36
+ return "<!-- AXML Parse Error: " + e.getMessage() + " -->";
37
+ }
36
38
  }
37
-
39
+
38
40
  private String parse(byte[] data) throws Exception {
39
- buffer = ByteBuffer.wrap(data);
40
- buffer.order(ByteOrder.LITTLE_ENDIAN);
41
- xml = new StringBuilder();
42
-
41
+ this.data = data;
42
+ this.position = 0;
43
+ this.xml = new StringBuilder();
44
+ this.xml.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
45
+
43
46
  // 读取文件头
44
- int magic = buffer.getInt();
47
+ int magic = readInt();
45
48
  if (magic != AXML_MAGIC) {
46
- throw new Exception("Invalid AXML magic: " + Integer.toHexString(magic));
49
+ throw new Exception("Invalid AXML magic: 0x" + Integer.toHexString(magic));
47
50
  }
48
-
49
- int fileSize = buffer.getInt();
50
-
51
- // 解析所有 chunks
52
- while (buffer.position() < data.length) {
53
- int chunkType = buffer.getInt();
54
- int chunkSize = buffer.getInt();
55
- int startPos = buffer.position() - 8;
56
-
51
+
52
+ int fileSize = readInt();
53
+
54
+ // 解析 chunks
55
+ while (position < data.length - 8) {
56
+ int chunkStart = position;
57
+ int chunkType = readShort();
58
+ int headerSize = readShort();
59
+ int chunkSize = readInt();
60
+
61
+ if (chunkSize <= 0 || chunkStart + chunkSize > data.length) {
62
+ break;
63
+ }
64
+
57
65
  switch (chunkType) {
58
- case CHUNK_STRING_POOL:
59
- parseStringPool(chunkSize);
60
- break;
61
- case CHUNK_RESOURCE_IDS:
62
- // 跳过资源 ID
63
- buffer.position(startPos + chunkSize);
66
+ case STRING_POOL_TYPE:
67
+ parseStringPool(chunkStart, chunkSize);
64
68
  break;
65
- case CHUNK_START_NAMESPACE:
66
- parseStartNamespace();
69
+ case XML_START_NAMESPACE:
70
+ case XML_END_NAMESPACE:
71
+ // 跳过命名空间
67
72
  break;
68
- case CHUNK_END_NAMESPACE:
69
- parseEndNamespace();
70
- break;
71
- case CHUNK_START_TAG:
73
+ case XML_START_TAG:
72
74
  parseStartTag();
73
75
  break;
74
- case CHUNK_END_TAG:
76
+ case XML_END_TAG:
75
77
  parseEndTag();
76
78
  break;
77
- case CHUNK_TEXT:
78
- parseText();
79
- break;
80
79
  default:
81
80
  // 跳过未知 chunk
82
- buffer.position(startPos + chunkSize);
83
81
  break;
84
82
  }
83
+
84
+ // 移动到下一个 chunk
85
+ position = chunkStart + chunkSize;
85
86
  }
86
-
87
+
87
88
  return xml.toString();
88
89
  }
89
-
90
- private void parseStringPool(int chunkSize) {
91
- int stringCount = buffer.getInt();
92
- int styleCount = buffer.getInt();
93
- int flags = buffer.getInt();
94
- int stringsOffset = buffer.getInt();
95
- int stylesOffset = buffer.getInt();
96
-
90
+
91
+ private void parseStringPool(int chunkStart, int chunkSize) {
92
+ int stringCount = readInt();
93
+ int styleCount = readInt();
94
+ int flags = readInt();
95
+ int stringsOffset = readInt();
96
+ int stylesOffset = readInt();
97
+
98
+ boolean isUtf8 = (flags & 0x100) != 0;
99
+
97
100
  // 读取字符串偏移表
98
- int[] stringOffsets = new int[stringCount];
101
+ int[] offsets = new int[stringCount];
99
102
  for (int i = 0; i < stringCount; i++) {
100
- stringOffsets[i] = buffer.getInt();
103
+ offsets[i] = readInt();
101
104
  }
102
-
103
- // 跳过样式偏移表
104
- for (int i = 0; i < styleCount; i++) {
105
- buffer.getInt();
106
- }
107
-
108
- // 读取字符串
109
- int stringsStart = buffer.position();
105
+
106
+ // 字符串数据开始位置
107
+ int stringsStart = chunkStart + stringsOffset;
110
108
  stringPool = new String[stringCount];
111
-
112
- boolean isUtf8 = (flags & 0x100) != 0;
113
-
109
+
114
110
  for (int i = 0; i < stringCount; i++) {
115
- buffer.position(stringsStart + stringOffsets[i]);
116
- stringPool[i] = readString(isUtf8);
111
+ int stringStart = stringsStart + offsets[i];
112
+ if (stringStart >= data.length) {
113
+ stringPool[i] = "";
114
+ continue;
115
+ }
116
+ stringPool[i] = readStringAt(stringStart, isUtf8);
117
117
  }
118
118
  }
119
-
120
- private String readString(boolean isUtf8) {
121
- if (isUtf8) {
122
- // UTF-8 编码
123
- int charLen = buffer.get() & 0xFF;
124
- if ((charLen & 0x80) != 0) {
125
- charLen = ((charLen & 0x7F) << 8) | (buffer.get() & 0xFF);
126
- }
127
- int byteLen = buffer.get() & 0xFF;
128
- if ((byteLen & 0x80) != 0) {
129
- byteLen = ((byteLen & 0x7F) << 8) | (buffer.get() & 0xFF);
130
- }
131
- byte[] bytes = new byte[byteLen];
132
- buffer.get(bytes);
133
- return new String(bytes, java.nio.charset.StandardCharsets.UTF_8);
134
- } else {
135
- // UTF-16 编码
136
- int charLen = buffer.getShort() & 0xFFFF;
137
- if ((charLen & 0x8000) != 0) {
138
- charLen = ((charLen & 0x7FFF) << 16) | (buffer.getShort() & 0xFFFF);
139
- }
140
- char[] chars = new char[charLen];
141
- for (int i = 0; i < charLen; i++) {
142
- chars[i] = buffer.getChar();
119
+
120
+ private String readStringAt(int pos, boolean isUtf8) {
121
+ try {
122
+ if (isUtf8) {
123
+ // UTF-8: 先读长度
124
+ int charLen = data[pos] & 0xFF;
125
+ int byteLen;
126
+ int dataStart;
127
+
128
+ if ((charLen & 0x80) != 0) {
129
+ charLen = ((charLen & 0x7F) << 8) | (data[pos + 1] & 0xFF);
130
+ byteLen = data[pos + 2] & 0xFF;
131
+ if ((byteLen & 0x80) != 0) {
132
+ byteLen = ((byteLen & 0x7F) << 8) | (data[pos + 3] & 0xFF);
133
+ dataStart = pos + 4;
134
+ } else {
135
+ dataStart = pos + 3;
136
+ }
137
+ } else {
138
+ byteLen = data[pos + 1] & 0xFF;
139
+ if ((byteLen & 0x80) != 0) {
140
+ byteLen = ((byteLen & 0x7F) << 8) | (data[pos + 2] & 0xFF);
141
+ dataStart = pos + 3;
142
+ } else {
143
+ dataStart = pos + 2;
144
+ }
145
+ }
146
+
147
+ if (dataStart + byteLen > data.length) {
148
+ byteLen = data.length - dataStart;
149
+ }
150
+ if (byteLen <= 0) return "";
151
+
152
+ return new String(data, dataStart, byteLen, java.nio.charset.StandardCharsets.UTF_8);
153
+ } else {
154
+ // UTF-16
155
+ int charLen = (data[pos] & 0xFF) | ((data[pos + 1] & 0xFF) << 8);
156
+ if ((charLen & 0x8000) != 0) {
157
+ int high = (data[pos + 2] & 0xFF) | ((data[pos + 3] & 0xFF) << 8);
158
+ charLen = ((charLen & 0x7FFF) << 16) | high;
159
+ pos += 4;
160
+ } else {
161
+ pos += 2;
162
+ }
163
+
164
+ if (pos + charLen * 2 > data.length) {
165
+ charLen = (data.length - pos) / 2;
166
+ }
167
+ if (charLen <= 0) return "";
168
+
169
+ char[] chars = new char[charLen];
170
+ for (int i = 0; i < charLen; i++) {
171
+ chars[i] = (char) ((data[pos + i * 2] & 0xFF) | ((data[pos + i * 2 + 1] & 0xFF) << 8));
172
+ }
173
+ return new String(chars);
143
174
  }
144
- return new String(chars);
175
+ } catch (Exception e) {
176
+ return "";
145
177
  }
146
178
  }
147
-
148
- private void parseStartNamespace() {
149
- int lineNumber = buffer.getInt();
150
- int comment = buffer.getInt();
151
- int prefix = buffer.getInt();
152
- int uri = buffer.getInt();
153
- }
154
-
155
- private void parseEndNamespace() {
156
- int lineNumber = buffer.getInt();
157
- int comment = buffer.getInt();
158
- int prefix = buffer.getInt();
159
- int uri = buffer.getInt();
160
- }
161
-
179
+
162
180
  private void parseStartTag() {
163
- int lineNumber = buffer.getInt();
164
- int comment = buffer.getInt();
165
- int namespaceUri = buffer.getInt();
166
- int name = buffer.getInt();
167
- int flags = buffer.getShort() & 0xFFFF;
168
- int attributeCount = buffer.getShort() & 0xFFFF;
169
- int classAttribute = buffer.getShort() & 0xFFFF;
170
- int idAttribute = buffer.getShort() & 0xFFFF;
171
- int styleAttribute = buffer.getShort() & 0xFFFF;
172
-
173
- // 添加缩进
181
+ int lineNumber = readInt();
182
+ int comment = readInt();
183
+ int ns = readInt();
184
+ int name = readInt();
185
+ int attrStart = readShort();
186
+ int attrSize = readShort();
187
+ int attrCount = readShort();
188
+ int idIndex = readShort();
189
+ int classIndex = readShort();
190
+ int styleIndex = readShort();
191
+
192
+ // 缩进
174
193
  for (int i = 0; i < indent; i++) {
175
- xml.append(" ");
194
+ xml.append(" ");
176
195
  }
177
-
178
- xml.append("<");
179
- xml.append(getString(name));
180
-
196
+
197
+ xml.append("<").append(getString(name));
198
+
181
199
  // 解析属性
182
- for (int i = 0; i < attributeCount; i++) {
183
- int attrNs = buffer.getInt();
184
- int attrName = buffer.getInt();
185
- int attrRawValue = buffer.getInt();
186
- int attrType = buffer.getShort() & 0xFFFF;
187
- buffer.getShort(); // size
188
- int attrData = buffer.getInt();
189
-
200
+ for (int i = 0; i < attrCount; i++) {
201
+ int attrNs = readInt();
202
+ int attrName = readInt();
203
+ int attrRawValue = readInt();
204
+ int attrTypeAndSize = readInt();
205
+ int attrData = readInt();
206
+
207
+ int attrType = (attrTypeAndSize >> 24) & 0xFF;
208
+
190
209
  xml.append("\n");
191
210
  for (int j = 0; j <= indent; j++) {
192
- xml.append(" ");
211
+ xml.append(" ");
193
212
  }
194
-
213
+
195
214
  // 命名空间前缀
196
- if (attrNs >= 0 && attrNs < stringPool.length) {
197
- String ns = stringPool[attrNs];
198
- if (ns.contains("android")) {
199
- xml.append("android:");
200
- }
215
+ String nsStr = getString(attrNs);
216
+ if (nsStr.contains("android")) {
217
+ xml.append("android:");
201
218
  }
202
-
203
- xml.append(getString(attrName));
204
- xml.append("=\"");
205
- xml.append(getAttributeValue(attrRawValue, attrType, attrData));
219
+
220
+ xml.append(getString(attrName)).append("=\"");
221
+ xml.append(escapeXml(getAttrValue(attrRawValue, attrType, attrData)));
206
222
  xml.append("\"");
207
223
  }
208
-
224
+
209
225
  xml.append(">\n");
210
226
  indent++;
211
227
  }
212
-
228
+
213
229
  private void parseEndTag() {
214
- int lineNumber = buffer.getInt();
215
- int comment = buffer.getInt();
216
- int namespaceUri = buffer.getInt();
217
- int name = buffer.getInt();
218
-
230
+ int lineNumber = readInt();
231
+ int comment = readInt();
232
+ int ns = readInt();
233
+ int name = readInt();
234
+
219
235
  indent--;
220
236
  for (int i = 0; i < indent; i++) {
221
- xml.append(" ");
222
- }
223
- xml.append("</");
224
- xml.append(getString(name));
225
- xml.append(">\n");
226
- }
227
-
228
- private void parseText() {
229
- int lineNumber = buffer.getInt();
230
- int comment = buffer.getInt();
231
- int text = buffer.getInt();
232
- buffer.getInt(); // unknown
233
- buffer.getInt(); // unknown
234
-
235
- for (int i = 0; i < indent; i++) {
236
- xml.append(" ");
237
+ xml.append(" ");
237
238
  }
238
- xml.append(getString(text));
239
- xml.append("\n");
239
+ xml.append("</").append(getString(name)).append(">\n");
240
240
  }
241
-
241
+
242
242
  private String getString(int index) {
243
- if (index >= 0 && index < stringPool.length) {
244
- return escapeXml(stringPool[index]);
243
+ if (stringPool != null && index >= 0 && index < stringPool.length) {
244
+ return stringPool[index] != null ? stringPool[index] : "";
245
245
  }
246
246
  return "";
247
247
  }
248
-
249
- private String getAttributeValue(int rawValue, int type, int data) {
250
- // 如果有原始字符串值
251
- if (rawValue >= 0 && rawValue < stringPool.length) {
252
- return escapeXml(stringPool[rawValue]);
248
+
249
+ private String getAttrValue(int rawValue, int type, int data) {
250
+ // 优先使用原始字符串
251
+ if (rawValue >= 0 && stringPool != null && rawValue < stringPool.length && stringPool[rawValue] != null) {
252
+ return stringPool[rawValue];
253
253
  }
254
-
255
- // 根据类型解析数据
256
- switch (type >> 8) {
257
- case 0x01: // 引用
258
- return "@" + Integer.toHexString(data);
259
- case 0x02: // 属性引用
260
- return "?" + Integer.toHexString(data);
261
- case 0x03: // 字符串
262
- if (data >= 0 && data < stringPool.length) {
263
- return escapeXml(stringPool[data]);
254
+
255
+ switch (type) {
256
+ case 0x01: return "@0x" + Integer.toHexString(data);
257
+ case 0x02: return "?0x" + Integer.toHexString(data);
258
+ case 0x03:
259
+ if (data >= 0 && stringPool != null && data < stringPool.length) {
260
+ return stringPool[data] != null ? stringPool[data] : "";
264
261
  }
265
262
  return "";
266
- case 0x10: // 整数
267
- return String.valueOf(data);
268
- case 0x11: // 十六进制
269
- return "0x" + Integer.toHexString(data);
270
- case 0x12: // 布尔
271
- return data != 0 ? "true" : "false";
272
- case 0x1C: // 颜色 ARGB8
273
- return String.format("#%08X", data);
274
- case 0x1D: // 颜色 RGB8
275
- return String.format("#%06X", data & 0xFFFFFF);
276
- default:
277
- return "0x" + Integer.toHexString(data);
263
+ case 0x10: return String.valueOf(data);
264
+ case 0x11: return "0x" + Integer.toHexString(data);
265
+ case 0x12: return data != 0 ? "true" : "false";
266
+ case 0x1C: return String.format("#%08X", data);
267
+ case 0x1D: return String.format("#%06X", data & 0xFFFFFF);
268
+ case 0x05: return String.format("%.1fdip", Float.intBitsToFloat(data));
269
+ case 0x06: return String.format("%.1fsp", Float.intBitsToFloat(data));
270
+ default: return "0x" + Integer.toHexString(data);
278
271
  }
279
272
  }
280
-
273
+
274
+ private int readInt() {
275
+ if (position + 4 > data.length) return 0;
276
+ int result = (data[position] & 0xFF) |
277
+ ((data[position + 1] & 0xFF) << 8) |
278
+ ((data[position + 2] & 0xFF) << 16) |
279
+ ((data[position + 3] & 0xFF) << 24);
280
+ position += 4;
281
+ return result;
282
+ }
283
+
284
+ private int readShort() {
285
+ if (position + 2 > data.length) return 0;
286
+ int result = (data[position] & 0xFF) | ((data[position + 1] & 0xFF) << 8);
287
+ position += 2;
288
+ return result;
289
+ }
290
+
281
291
  private String escapeXml(String s) {
282
292
  if (s == null) return "";
283
293
  return s.replace("&", "&amp;")
284
294
  .replace("<", "&lt;")
285
295
  .replace(">", "&gt;")
286
- .replace("\"", "&quot;")
287
- .replace("'", "&apos;");
296
+ .replace("\"", "&quot;");
288
297
  }
289
298
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "capacitor-dex-editor",
3
- "version": "0.0.29",
3
+ "version": "0.0.31",
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",