capacitor-dex-editor 0.0.37 → 0.0.39

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.
Files changed (67) hide show
  1. package/android/src/main/AndroidManifest.xml +8 -0
  2. package/android/src/main/assets/availableSyntax.json +54 -0
  3. package/android/src/main/assets/c.json +42 -0
  4. package/android/src/main/assets/colors.json +21 -0
  5. package/android/src/main/assets/cpp.json +79 -0
  6. package/android/src/main/assets/dart.json +108 -0
  7. package/android/src/main/assets/java.json +46 -0
  8. package/android/src/main/assets/js.json +54 -0
  9. package/android/src/main/assets/json.json +33 -0
  10. package/android/src/main/assets/kotlin.json +53 -0
  11. package/android/src/main/assets/lua.json +54 -0
  12. package/android/src/main/assets/php.json +69 -0
  13. package/android/src/main/assets/python.json +87 -0
  14. package/android/src/main/assets/rust.json +139 -0
  15. package/android/src/main/assets/smali.json +131 -0
  16. package/android/src/main/assets/xml.json +96 -0
  17. package/android/src/main/java/com/aetherlink/dexeditor/DexEditorPluginPlugin.java +67 -0
  18. package/android/src/main/java/com/aetherlink/dexeditor/DexManager.java +165 -0
  19. package/android/src/main/java/com/aetherlink/dexeditor/SmaliEditorActivity.java +205 -0
  20. package/android/src/main/java/com/aetherlink/dexeditor/editor/EditView.java +4022 -0
  21. package/android/src/main/java/com/aetherlink/dexeditor/editor/WordWrapLayout.java +275 -0
  22. package/android/src/main/java/com/aetherlink/dexeditor/editor/buffer/BufferCache.java +113 -0
  23. package/android/src/main/java/com/aetherlink/dexeditor/editor/buffer/GapBuffer.java +685 -0
  24. package/android/src/main/java/com/aetherlink/dexeditor/editor/component/ClipboardPanel.java +1380 -0
  25. package/android/src/main/java/com/aetherlink/dexeditor/editor/component/Magnifier.java +363 -0
  26. package/android/src/main/java/com/aetherlink/dexeditor/editor/highlight/Candidate.java +52 -0
  27. package/android/src/main/java/com/aetherlink/dexeditor/editor/highlight/CommentDef.java +47 -0
  28. package/android/src/main/java/com/aetherlink/dexeditor/editor/highlight/LineResult.java +49 -0
  29. package/android/src/main/java/com/aetherlink/dexeditor/editor/highlight/MHSyntaxHighlightEngine.java +841 -0
  30. package/android/src/main/java/com/aetherlink/dexeditor/editor/highlight/Rule.java +53 -0
  31. package/android/src/main/java/com/aetherlink/dexeditor/editor/highlight/Token.java +48 -0
  32. package/android/src/main/java/com/aetherlink/dexeditor/editor/listener/OnTextChangedListener.java +6 -0
  33. package/android/src/main/java/com/aetherlink/dexeditor/editor/utils/Pair.java +80 -0
  34. package/android/src/main/java/com/aetherlink/dexeditor/editor/utils/ScreenUtils.java +63 -0
  35. package/android/src/main/res/drawable/abc_text_cursor_material.xml +9 -0
  36. package/android/src/main/res/drawable/abc_text_select_handle_left_mtrl.png +0 -0
  37. package/android/src/main/res/drawable/abc_text_select_handle_middle_mtrl.png +0 -0
  38. package/android/src/main/res/drawable/abc_text_select_handle_right_mtrl.png +0 -0
  39. package/android/src/main/res/drawable/ic_arrow_back.xml +12 -0
  40. package/android/src/main/res/drawable/ic_copy.xml +11 -0
  41. package/android/src/main/res/drawable/ic_cut.xml +10 -0
  42. package/android/src/main/res/drawable/ic_delete.xml +12 -0
  43. package/android/src/main/res/drawable/ic_edit_white_24dp.xml +10 -0
  44. package/android/src/main/res/drawable/ic_goto.xml +10 -0
  45. package/android/src/main/res/drawable/ic_launcher_background.xml +186 -0
  46. package/android/src/main/res/drawable/ic_look_white_24dp.xml +11 -0
  47. package/android/src/main/res/drawable/ic_more.xml +10 -0
  48. package/android/src/main/res/drawable/ic_open_link.xml +11 -0
  49. package/android/src/main/res/drawable/ic_paste.xml +11 -0
  50. package/android/src/main/res/drawable/ic_redo_white_24dp.xml +10 -0
  51. package/android/src/main/res/drawable/ic_select.xml +11 -0
  52. package/android/src/main/res/drawable/ic_select_all.xml +10 -0
  53. package/android/src/main/res/drawable/ic_share.xml +11 -0
  54. package/android/src/main/res/drawable/ic_toggle_comment.xml +12 -0
  55. package/android/src/main/res/drawable/ic_translate.xml +10 -0
  56. package/android/src/main/res/drawable/ic_undo_white_24dp.xml +11 -0
  57. package/android/src/main/res/drawable/magnifier_bg.xml +5 -0
  58. package/android/src/main/res/drawable/popup_background.xml +8 -0
  59. package/android/src/main/res/drawable/ripple_effect.xml +9 -0
  60. package/android/src/main/res/drawable/selection_menu_background.xml +5 -0
  61. package/android/src/main/res/layout/custom_selection_menu.xml +44 -0
  62. package/android/src/main/res/layout/expand_button.xml +23 -0
  63. package/android/src/main/res/layout/item_autocomplete.xml +25 -0
  64. package/android/src/main/res/layout/magnifier_popup.xml +17 -0
  65. package/android/src/main/res/layout/menu_item.xml +30 -0
  66. package/android/src/main/res/layout/text_selection_menu.xml +36 -0
  67. package/package.json +1 -1
@@ -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
  */
@@ -0,0 +1,205 @@
1
+ package com.aetherlink.dexeditor;
2
+
3
+ import android.app.Activity;
4
+ import android.content.Intent;
5
+ import android.graphics.Color;
6
+ import android.graphics.Typeface;
7
+ import android.os.Bundle;
8
+ import android.view.View;
9
+ import android.view.ViewGroup;
10
+ import android.view.Window;
11
+ import android.view.WindowManager;
12
+ import android.widget.FrameLayout;
13
+ import android.widget.ImageButton;
14
+ import android.widget.LinearLayout;
15
+ import android.widget.TextView;
16
+ import android.widget.Toast;
17
+
18
+ import com.aetherlink.dexeditor.editor.EditView;
19
+
20
+ public class SmaliEditorActivity extends Activity {
21
+
22
+ public static final String EXTRA_CONTENT = "content";
23
+ public static final String EXTRA_TITLE = "title";
24
+ public static final String EXTRA_CLASS_NAME = "className";
25
+ public static final String EXTRA_READ_ONLY = "readOnly";
26
+ public static final String RESULT_CONTENT = "content";
27
+ public static final String RESULT_MODIFIED = "modified";
28
+
29
+ private EditView editView;
30
+ private String originalContent;
31
+ private boolean readOnly = false;
32
+
33
+ @Override
34
+ protected void onCreate(Bundle savedInstanceState) {
35
+ super.onCreate(savedInstanceState);
36
+
37
+ // 全屏
38
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
39
+ getWindow().setFlags(
40
+ WindowManager.LayoutParams.FLAG_FULLSCREEN,
41
+ WindowManager.LayoutParams.FLAG_FULLSCREEN
42
+ );
43
+
44
+ // 获取参数
45
+ Intent intent = getIntent();
46
+ originalContent = intent.getStringExtra(EXTRA_CONTENT);
47
+ String title = intent.getStringExtra(EXTRA_TITLE);
48
+ String className = intent.getStringExtra(EXTRA_CLASS_NAME);
49
+ readOnly = intent.getBooleanExtra(EXTRA_READ_ONLY, false);
50
+
51
+ if (originalContent == null) originalContent = "";
52
+ if (title == null) title = "Smali Editor";
53
+
54
+ // 创建布局
55
+ LinearLayout root = new LinearLayout(this);
56
+ root.setOrientation(LinearLayout.VERTICAL);
57
+ root.setBackgroundColor(Color.parseColor("#1E1E1E"));
58
+
59
+ // 工具栏
60
+ LinearLayout toolbar = createToolbar(title, className);
61
+ root.addView(toolbar);
62
+
63
+ // 编辑器
64
+ editView = new EditView(this);
65
+ editView.setLayoutParams(new LinearLayout.LayoutParams(
66
+ ViewGroup.LayoutParams.MATCH_PARENT,
67
+ ViewGroup.LayoutParams.MATCH_PARENT
68
+ ));
69
+ editView.setText(originalContent);
70
+ editView.setSyntaxLanguageFileName("smali.json");
71
+ editView.setEditedMode(!readOnly);
72
+ editView.setTypeface(Typeface.MONOSPACE);
73
+ editView.setTextSize(14);
74
+
75
+ root.addView(editView);
76
+ setContentView(root);
77
+ }
78
+
79
+ private LinearLayout createToolbar(String title, String className) {
80
+ LinearLayout toolbar = new LinearLayout(this);
81
+ toolbar.setOrientation(LinearLayout.HORIZONTAL);
82
+ toolbar.setBackgroundColor(Color.parseColor("#2D2D2D"));
83
+ toolbar.setPadding(16, 8, 16, 8);
84
+ LinearLayout.LayoutParams toolbarParams = new LinearLayout.LayoutParams(
85
+ ViewGroup.LayoutParams.MATCH_PARENT,
86
+ ViewGroup.LayoutParams.WRAP_CONTENT
87
+ );
88
+ toolbar.setLayoutParams(toolbarParams);
89
+
90
+ // 返回按钮
91
+ ImageButton backBtn = createIconButton("←");
92
+ backBtn.setOnClickListener(v -> handleBack());
93
+ toolbar.addView(backBtn);
94
+
95
+ // 标题
96
+ TextView titleView = new TextView(this);
97
+ titleView.setText(title);
98
+ titleView.setTextColor(Color.WHITE);
99
+ titleView.setTextSize(16);
100
+ titleView.setPadding(16, 0, 16, 0);
101
+ LinearLayout.LayoutParams titleParams = new LinearLayout.LayoutParams(
102
+ 0, ViewGroup.LayoutParams.WRAP_CONTENT, 1
103
+ );
104
+ titleView.setLayoutParams(titleParams);
105
+ toolbar.addView(titleView);
106
+
107
+ // 撤销按钮
108
+ ImageButton undoBtn = createIconButton("↶");
109
+ undoBtn.setOnClickListener(v -> {
110
+ if (editView.canUndo()) {
111
+ editView.undo();
112
+ }
113
+ });
114
+ toolbar.addView(undoBtn);
115
+
116
+ // 重做按钮
117
+ ImageButton redoBtn = createIconButton("↷");
118
+ redoBtn.setOnClickListener(v -> {
119
+ if (editView.canRedo()) {
120
+ editView.redo();
121
+ }
122
+ });
123
+ toolbar.addView(redoBtn);
124
+
125
+ // 保存按钮
126
+ if (!readOnly) {
127
+ ImageButton saveBtn = createIconButton("✓");
128
+ saveBtn.setOnClickListener(v -> saveAndFinish());
129
+ toolbar.addView(saveBtn);
130
+ }
131
+
132
+ return toolbar;
133
+ }
134
+
135
+ private ImageButton createIconButton(String icon) {
136
+ ImageButton btn = new ImageButton(this);
137
+ btn.setBackgroundColor(Color.TRANSPARENT);
138
+ btn.setPadding(24, 16, 24, 16);
139
+
140
+ // 使用 TextView 作为图标
141
+ TextView tv = new TextView(this);
142
+ tv.setText(icon);
143
+ tv.setTextColor(Color.WHITE);
144
+ tv.setTextSize(20);
145
+
146
+ FrameLayout container = new FrameLayout(this);
147
+ container.addView(tv);
148
+
149
+ // 返回简单按钮
150
+ btn.setImageDrawable(null);
151
+ btn.setContentDescription(icon);
152
+
153
+ // 实际返回一个带文字的按钮
154
+ ImageButton textBtn = new ImageButton(this) {
155
+ @Override
156
+ protected void onDraw(android.graphics.Canvas canvas) {
157
+ super.onDraw(canvas);
158
+ android.graphics.Paint paint = new android.graphics.Paint();
159
+ paint.setColor(Color.WHITE);
160
+ paint.setTextSize(48);
161
+ paint.setTextAlign(android.graphics.Paint.Align.CENTER);
162
+ canvas.drawText(icon, getWidth() / 2f, getHeight() / 2f + 16, paint);
163
+ }
164
+ };
165
+ textBtn.setBackgroundColor(Color.TRANSPARENT);
166
+ textBtn.setMinimumWidth(96);
167
+ textBtn.setMinimumHeight(96);
168
+
169
+ return textBtn;
170
+ }
171
+
172
+ private void handleBack() {
173
+ String currentContent = editView.getText();
174
+ if (!currentContent.equals(originalContent)) {
175
+ // 有修改,询问是否保存
176
+ new android.app.AlertDialog.Builder(this)
177
+ .setTitle("保存修改?")
178
+ .setMessage("内容已修改,是否保存?")
179
+ .setPositiveButton("保存", (d, w) -> saveAndFinish())
180
+ .setNegativeButton("放弃", (d, w) -> {
181
+ setResult(RESULT_CANCELED);
182
+ finish();
183
+ })
184
+ .setNeutralButton("取消", null)
185
+ .show();
186
+ } else {
187
+ setResult(RESULT_CANCELED);
188
+ finish();
189
+ }
190
+ }
191
+
192
+ private void saveAndFinish() {
193
+ String currentContent = editView.getText();
194
+ Intent result = new Intent();
195
+ result.putExtra(RESULT_CONTENT, currentContent);
196
+ result.putExtra(RESULT_MODIFIED, !currentContent.equals(originalContent));
197
+ setResult(RESULT_OK, result);
198
+ finish();
199
+ }
200
+
201
+ @Override
202
+ public void onBackPressed() {
203
+ handleBack();
204
+ }
205
+ }