capacitor-dex-editor 0.0.69 → 0.0.70

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 (40) hide show
  1. package/android/build.gradle +22 -0
  2. package/android/src/main/cpp/CMakeLists.txt +57 -0
  3. package/android/src/main/cpp/apk/apk_handler.cpp +121 -0
  4. package/android/src/main/cpp/apk/zip_utils.cpp +425 -0
  5. package/android/src/main/cpp/arsc/arsc_parser.cpp +390 -0
  6. package/android/src/main/cpp/dex/dex_builder.cpp +752 -0
  7. package/android/src/main/cpp/dex/dex_parser.cpp +620 -0
  8. package/android/src/main/cpp/dex/smali_disasm.cpp +1223 -0
  9. package/android/src/main/cpp/dex/smali_to_java.cpp +576 -0
  10. package/android/src/main/cpp/include/apk/apk_handler.h +41 -0
  11. package/android/src/main/cpp/include/apk/zip_utils.h +57 -0
  12. package/android/src/main/cpp/include/arsc/arsc_parser.h +98 -0
  13. package/android/src/main/cpp/include/dex/dex_builder.h +189 -0
  14. package/android/src/main/cpp/include/dex/dex_parser.h +137 -0
  15. package/android/src/main/cpp/include/dex/smali_disasm.h +127 -0
  16. package/android/src/main/cpp/include/dex/smali_to_java.h +50 -0
  17. package/android/src/main/cpp/include/xml/android_resources.h +495 -0
  18. package/android/src/main/cpp/include/xml/axml_parser.h +147 -0
  19. package/android/src/main/cpp/jni_bridge.cpp +872 -0
  20. package/android/src/main/cpp/third_party/miniz.c +646 -0
  21. package/android/src/main/cpp/third_party/miniz.h +605 -0
  22. package/android/src/main/cpp/third_party/miniz_common.h +97 -0
  23. package/android/src/main/cpp/third_party/miniz_export.h +6 -0
  24. package/android/src/main/cpp/third_party/miniz_tdef.c +1597 -0
  25. package/android/src/main/cpp/third_party/miniz_tdef.h +199 -0
  26. package/android/src/main/cpp/third_party/miniz_tinfl.c +770 -0
  27. package/android/src/main/cpp/third_party/miniz_tinfl.h +150 -0
  28. package/android/src/main/cpp/third_party/miniz_zip.c +4895 -0
  29. package/android/src/main/cpp/third_party/miniz_zip.h +454 -0
  30. package/android/src/main/cpp/third_party/nlohmann_json/CMakeLists.txt +0 -0
  31. package/android/src/main/cpp/third_party/nlohmann_json/single_include/nlohmann/json.hpp +24765 -0
  32. package/android/src/main/cpp/xml/axml_parser.cpp +1701 -0
  33. package/android/src/main/java/com/aetherlink/dexeditor/CppDex.java +295 -0
  34. package/android/src/main/java/com/aetherlink/dexeditor/DexManager.java +20 -20
  35. package/package.json +1 -1
  36. package/android/src/main/java/com/aetherlink/dexeditor/RustDex.java +0 -203
  37. package/android/src/main/jniLibs/arm64-v8a/libdex_rust.so +0 -0
  38. package/android/src/main/jniLibs/armeabi-v7a/libdex_rust.so +0 -0
  39. package/android/src/main/jniLibs/x86/libdex_rust.so +0 -0
  40. package/android/src/main/jniLibs/x86_64/libdex_rust.so +0 -0
@@ -20,12 +20,34 @@ apply plugin: 'com.android.library'
20
20
  android {
21
21
  namespace "com.aetherlink.dexeditor"
22
22
  compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35
23
+ ndkVersion "25.2.9519653"
23
24
  defaultConfig {
24
25
  minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23
25
26
  targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35
26
27
  versionCode 1
27
28
  versionName "1.0"
28
29
  testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
30
+
31
+ // NDK 配置
32
+ externalNativeBuild {
33
+ cmake {
34
+ cppFlags "-std=c++17 -O2"
35
+ arguments "-DANDROID_STL=c++_shared"
36
+ }
37
+ }
38
+
39
+ // 支持的 ABI
40
+ ndk {
41
+ abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64'
42
+ }
43
+ }
44
+
45
+ // CMake 构建配置
46
+ externalNativeBuild {
47
+ cmake {
48
+ path "src/main/cpp/CMakeLists.txt"
49
+ version "3.22.1"
50
+ }
29
51
  }
30
52
  buildTypes {
31
53
  release {
@@ -0,0 +1,57 @@
1
+ cmake_minimum_required(VERSION 3.18)
2
+ project(dex_cpp VERSION 1.0.0 LANGUAGES C CXX)
3
+
4
+ set(CMAKE_CXX_STANDARD 17)
5
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
6
+
7
+ # 源文件
8
+ set(SOURCES
9
+ # DEX 操作
10
+ dex/dex_parser.cpp
11
+ dex/dex_builder.cpp
12
+ dex/smali_disasm.cpp
13
+ dex/smali_to_java.cpp
14
+ # XML 操作
15
+ xml/axml_parser.cpp
16
+ # ARSC 操作
17
+ arsc/arsc_parser.cpp
18
+ # APK 操作
19
+ apk/apk_handler.cpp
20
+ apk/zip_utils.cpp
21
+ # miniz (ZIP 库)
22
+ third_party/miniz.c
23
+ third_party/miniz_tinfl.c
24
+ third_party/miniz_tdef.c
25
+ third_party/miniz_zip.c
26
+ # JNI 绑定
27
+ jni_bridge.cpp
28
+ )
29
+
30
+ # 头文件目录
31
+ include_directories(
32
+ ${CMAKE_CURRENT_SOURCE_DIR}/include
33
+ ${CMAKE_CURRENT_SOURCE_DIR}/third_party
34
+ ${CMAKE_CURRENT_SOURCE_DIR}/third_party/nlohmann_json/single_include
35
+ )
36
+
37
+ # 编译为共享库
38
+ add_library(dex_cpp SHARED ${SOURCES})
39
+
40
+ # 查找 Android log 库
41
+ find_library(log-lib log)
42
+
43
+ # 链接库
44
+ target_link_libraries(dex_cpp ${log-lib})
45
+
46
+ # 编译选项
47
+ target_compile_options(dex_cpp PRIVATE
48
+ -Wall
49
+ -Wextra
50
+ -O2
51
+ -fvisibility=hidden
52
+ )
53
+
54
+ # 定义宏
55
+ target_compile_definitions(dex_cpp PRIVATE
56
+ ANDROID
57
+ )
@@ -0,0 +1,121 @@
1
+ #include "apk/apk_handler.h"
2
+ #include "apk/zip_utils.h"
3
+ #include <algorithm>
4
+
5
+ namespace apk {
6
+
7
+ bool ApkHandler::open(const std::string& path) {
8
+ ZipReader reader;
9
+ if (!reader.open(path)) {
10
+ return false;
11
+ }
12
+
13
+ path_ = path;
14
+ entries_.clear();
15
+
16
+ reader.extract_all([this](const std::string& name, const std::vector<uint8_t>& data) {
17
+ FileEntry entry;
18
+ entry.name = name;
19
+ entry.data = data;
20
+ entry.is_directory = !name.empty() && name.back() == '/';
21
+ entries_.push_back(entry);
22
+ });
23
+
24
+ reader.close();
25
+ is_open_ = true;
26
+ return true;
27
+ }
28
+
29
+ bool ApkHandler::create(const std::string& path) {
30
+ path_ = path;
31
+ entries_.clear();
32
+ is_open_ = true;
33
+ return true;
34
+ }
35
+
36
+ bool ApkHandler::save(const std::string& path) {
37
+ if (!is_open_) return false;
38
+
39
+ ZipWriter writer;
40
+
41
+ for (const auto& entry : entries_) {
42
+ if (!entry.is_directory) {
43
+ // Let ZipWriter handle compression decisions
44
+ writer.add_file(entry.name, entry.data, true);
45
+ }
46
+ }
47
+
48
+ return writer.save(path);
49
+ }
50
+
51
+ void ApkHandler::close() {
52
+ entries_.clear();
53
+ path_.clear();
54
+ is_open_ = false;
55
+ }
56
+
57
+ std::vector<std::string> ApkHandler::list_files() const {
58
+ std::vector<std::string> names;
59
+ for (const auto& entry : entries_) {
60
+ names.push_back(entry.name);
61
+ }
62
+ return names;
63
+ }
64
+
65
+ bool ApkHandler::extract_file(const std::string& name, std::vector<uint8_t>& data) const {
66
+ for (const auto& entry : entries_) {
67
+ if (entry.name == name) {
68
+ data = entry.data;
69
+ return true;
70
+ }
71
+ }
72
+ return false;
73
+ }
74
+
75
+ bool ApkHandler::replace_file(const std::string& name, const std::vector<uint8_t>& data) {
76
+ for (auto& entry : entries_) {
77
+ if (entry.name == name) {
78
+ entry.data = data;
79
+ return true;
80
+ }
81
+ }
82
+ return false;
83
+ }
84
+
85
+ bool ApkHandler::add_file(const std::string& name, const std::vector<uint8_t>& data) {
86
+ for (const auto& entry : entries_) {
87
+ if (entry.name == name) {
88
+ return false;
89
+ }
90
+ }
91
+
92
+ FileEntry entry;
93
+ entry.name = name;
94
+ entry.data = data;
95
+ entry.is_directory = false;
96
+ entries_.push_back(entry);
97
+ return true;
98
+ }
99
+
100
+ bool ApkHandler::delete_file(const std::string& name) {
101
+ auto it = std::remove_if(entries_.begin(), entries_.end(),
102
+ [&name](const FileEntry& entry) { return entry.name == name; });
103
+
104
+ if (it != entries_.end()) {
105
+ entries_.erase(it, entries_.end());
106
+ return true;
107
+ }
108
+ return false;
109
+ }
110
+
111
+ void ApkHandler::remove_files_by_pattern(const std::string& pattern) {
112
+ entries_.erase(
113
+ std::remove_if(entries_.begin(), entries_.end(),
114
+ [&pattern](const FileEntry& entry) {
115
+ return entry.name.find(pattern) != std::string::npos;
116
+ }),
117
+ entries_.end()
118
+ );
119
+ }
120
+
121
+ } // namespace apk
@@ -0,0 +1,425 @@
1
+ #include "apk/zip_utils.h"
2
+ #include <fstream>
3
+ #include <cstring>
4
+ #include <algorithm>
5
+ #include <mutex>
6
+ #include <memory>
7
+ #include <cctype>
8
+ #ifdef _WIN32
9
+ #include <windows.h>
10
+ #endif
11
+
12
+ #define MINIZ_NO_STDIO
13
+ #define MINIZ_NO_ARCHIVE_APIS
14
+ #define MINIZ_NO_TIME
15
+ #define MINIZ_NO_ZLIB_APIS
16
+ #include "miniz.h"
17
+
18
+ namespace apk {
19
+
20
+ // ZIP format constants
21
+ static constexpr uint32_t ZIP_LOCAL_FILE_HEADER_SIG = 0x04034b50;
22
+ static constexpr uint32_t ZIP_CENTRAL_DIR_SIG = 0x02014b50;
23
+ static constexpr uint32_t ZIP_END_CENTRAL_DIR_SIG = 0x06054b50;
24
+
25
+ // ZIP structure sizes
26
+ static constexpr size_t ZIP_LOCAL_HEADER_SIZE = 30;
27
+ static constexpr size_t ZIP_CENTRAL_DIR_ENTRY_SIZE = 46;
28
+ static constexpr size_t ZIP_EOCD_SIZE = 22;
29
+
30
+ // RAII wrapper for miniz allocations
31
+ struct MzDeleter {
32
+ void operator()(void* p) const { if (p) mz_free(p); }
33
+ };
34
+ using MzUniquePtr = std::unique_ptr<void, MzDeleter>;
35
+
36
+ // Thread-safe CRC32 table initialization
37
+ static std::once_flag crc32_init_flag;
38
+ static uint32_t crc32_table[256];
39
+
40
+ static void init_crc32_table() {
41
+ std::call_once(crc32_init_flag, []() {
42
+ for (uint32_t i = 0; i < 256; i++) {
43
+ uint32_t c = i;
44
+ for (int j = 0; j < 8; j++) {
45
+ c = (c & 1) ? (0xEDB88320 ^ (c >> 1)) : (c >> 1);
46
+ }
47
+ crc32_table[i] = c;
48
+ }
49
+ });
50
+ }
51
+
52
+ static uint32_t calc_crc32(const uint8_t* data, size_t len) {
53
+ init_crc32_table();
54
+ uint32_t crc = 0xFFFFFFFF;
55
+ for (size_t i = 0; i < len; i++) {
56
+ crc = crc32_table[(crc ^ data[i]) & 0xFF] ^ (crc >> 8);
57
+ }
58
+ return crc ^ 0xFFFFFFFF;
59
+ }
60
+
61
+ template<typename T>
62
+ static T read_le(const uint8_t* p) {
63
+ T val = 0;
64
+ for (size_t i = 0; i < sizeof(T); i++) {
65
+ val |= static_cast<T>(p[i]) << (i * 8);
66
+ }
67
+ return val;
68
+ }
69
+
70
+ template<typename T>
71
+ static void write_le(uint8_t* p, T val) {
72
+ for (size_t i = 0; i < sizeof(T); i++) {
73
+ p[i] = static_cast<uint8_t>(val >> (i * 8));
74
+ }
75
+ }
76
+
77
+ static std::vector<uint8_t> deflate_compress(const std::vector<uint8_t>& input) {
78
+ if (input.empty()) return input;
79
+
80
+ size_t out_len = 0;
81
+ // Use maximum compression level (MZ_BEST_COMPRESSION = 9)
82
+ // TDEFL_WRITE_ZLIB_HEADER is NOT set - we want raw deflate for ZIP
83
+ int flags = tdefl_create_comp_flags_from_zip_params(9, -15, MZ_DEFAULT_STRATEGY);
84
+ MzUniquePtr pComp(tdefl_compress_mem_to_heap(input.data(), input.size(), &out_len, flags));
85
+
86
+ if (!pComp) {
87
+ return input;
88
+ }
89
+
90
+ auto* ptr = static_cast<uint8_t*>(pComp.get());
91
+ return std::vector<uint8_t>(ptr, ptr + out_len);
92
+ }
93
+
94
+ static std::vector<uint8_t> deflate_decompress(const uint8_t* data, size_t compressed_size, size_t uncompressed_size) {
95
+ if (compressed_size == 0 || uncompressed_size == 0) {
96
+ return {};
97
+ }
98
+
99
+ size_t out_len = 0;
100
+ MzUniquePtr pDecomp(tinfl_decompress_mem_to_heap(data, compressed_size, &out_len, 0));
101
+
102
+ if (!pDecomp) {
103
+ return {};
104
+ }
105
+
106
+ auto* ptr = static_cast<uint8_t*>(pDecomp.get());
107
+ return std::vector<uint8_t>(ptr, ptr + out_len);
108
+ }
109
+
110
+ bool ZipReader::open(const std::string& path) {
111
+ std::ifstream file;
112
+ #ifdef _WIN32
113
+ // Convert UTF-8 path to wide string for Windows
114
+ int wlen = MultiByteToWideChar(CP_UTF8, 0, path.c_str(), (int)path.size(), nullptr, 0);
115
+ if (wlen > 0) {
116
+ std::wstring wpath(wlen, 0);
117
+ MultiByteToWideChar(CP_UTF8, 0, path.c_str(), (int)path.size(), &wpath[0], wlen);
118
+ file.open(wpath, std::ios::binary);
119
+ }
120
+ #else
121
+ file.open(path, std::ios::binary);
122
+ #endif
123
+ if (!file.is_open()) return false;
124
+
125
+ file.seekg(0, std::ios::end);
126
+ size_t size = file.tellg();
127
+ file.seekg(0, std::ios::beg);
128
+
129
+ data_.resize(size);
130
+ file.read(reinterpret_cast<char*>(data_.data()), size);
131
+ file.close();
132
+
133
+ return parse_central_directory();
134
+ }
135
+
136
+ bool ZipReader::open(const std::vector<uint8_t>& data) {
137
+ data_ = data;
138
+ return parse_central_directory();
139
+ }
140
+
141
+ void ZipReader::close() {
142
+ data_.clear();
143
+ entries_.clear();
144
+ is_open_ = false;
145
+ }
146
+
147
+ bool ZipReader::parse_central_directory() {
148
+ if (data_.size() < ZIP_EOCD_SIZE) return false;
149
+
150
+ // Find End of Central Directory record
151
+ size_t pos = data_.size() - ZIP_EOCD_SIZE;
152
+ while (pos > 0) {
153
+ if (read_le<uint32_t>(&data_[pos]) == ZIP_END_CENTRAL_DIR_SIG) {
154
+ break;
155
+ }
156
+ pos--;
157
+ }
158
+
159
+ if (read_le<uint32_t>(&data_[pos]) != ZIP_END_CENTRAL_DIR_SIG) {
160
+ return false;
161
+ }
162
+
163
+ // Bounds check for EOCD fields
164
+ if (pos + ZIP_EOCD_SIZE > data_.size()) return false;
165
+
166
+ uint16_t num_entries = read_le<uint16_t>(&data_[pos + 10]);
167
+ uint32_t central_dir_offset = read_le<uint32_t>(&data_[pos + 16]);
168
+
169
+ size_t offset = central_dir_offset;
170
+ entries_.clear();
171
+
172
+ for (uint16_t i = 0; i < num_entries; i++) {
173
+ // Bounds check for central directory entry header
174
+ if (offset + ZIP_CENTRAL_DIR_ENTRY_SIZE > data_.size()) break;
175
+
176
+ if (read_le<uint32_t>(&data_[offset]) != ZIP_CENTRAL_DIR_SIG) {
177
+ break;
178
+ }
179
+
180
+ ZipEntry entry;
181
+ entry.compression_method = read_le<uint16_t>(&data_[offset + 10]);
182
+ entry.crc32 = read_le<uint32_t>(&data_[offset + 16]);
183
+ entry.compressed_size = read_le<uint32_t>(&data_[offset + 20]);
184
+ entry.uncompressed_size = read_le<uint32_t>(&data_[offset + 24]);
185
+
186
+ uint16_t name_len = read_le<uint16_t>(&data_[offset + 28]);
187
+ uint16_t extra_len = read_le<uint16_t>(&data_[offset + 30]);
188
+ uint16_t comment_len = read_le<uint16_t>(&data_[offset + 32]);
189
+
190
+ // Bounds check for variable-length fields
191
+ size_t entry_total_size = ZIP_CENTRAL_DIR_ENTRY_SIZE + name_len + extra_len + comment_len;
192
+ if (offset + entry_total_size > data_.size()) break;
193
+
194
+ entry.local_header_offset = read_le<uint32_t>(&data_[offset + 42]);
195
+ entry.name = std::string(reinterpret_cast<char*>(&data_[offset + ZIP_CENTRAL_DIR_ENTRY_SIZE]), name_len);
196
+
197
+ entries_.push_back(std::move(entry));
198
+ offset += entry_total_size;
199
+ }
200
+
201
+ is_open_ = true;
202
+ return true;
203
+ }
204
+
205
+ std::vector<std::string> ZipReader::list() const {
206
+ std::vector<std::string> names;
207
+ for (const auto& entry : entries_) {
208
+ names.push_back(entry.name);
209
+ }
210
+ return names;
211
+ }
212
+
213
+ bool ZipReader::extract(const std::string& name, std::vector<uint8_t>& out) const {
214
+ for (const auto& entry : entries_) {
215
+ if (entry.name == name) {
216
+ size_t offset = entry.local_header_offset;
217
+
218
+ // Bounds check for local file header
219
+ if (offset + ZIP_LOCAL_HEADER_SIZE > data_.size()) {
220
+ return false;
221
+ }
222
+
223
+ if (read_le<uint32_t>(&data_[offset]) != ZIP_LOCAL_FILE_HEADER_SIG) {
224
+ return false;
225
+ }
226
+
227
+ uint16_t name_len = read_le<uint16_t>(&data_[offset + 26]);
228
+ uint16_t extra_len = read_le<uint16_t>(&data_[offset + 28]);
229
+
230
+ size_t data_offset = offset + ZIP_LOCAL_HEADER_SIZE + name_len + extra_len;
231
+
232
+ // Bounds check for file data
233
+ if (data_offset + entry.compressed_size > data_.size()) {
234
+ return false;
235
+ }
236
+
237
+ if (entry.compression_method == 0) {
238
+ out.resize(entry.uncompressed_size);
239
+ std::memcpy(out.data(), &data_[data_offset], entry.uncompressed_size);
240
+ } else if (entry.compression_method == 8) {
241
+ out = deflate_decompress(&data_[data_offset], entry.compressed_size, entry.uncompressed_size);
242
+ } else {
243
+ return false;
244
+ }
245
+
246
+ return true;
247
+ }
248
+ }
249
+ return false;
250
+ }
251
+
252
+ bool ZipReader::extract_all(std::function<void(const std::string&, const std::vector<uint8_t>&)> callback) const {
253
+ for (const auto& entry : entries_) {
254
+ std::vector<uint8_t> data;
255
+ if (extract(entry.name, data)) {
256
+ callback(entry.name, data);
257
+ }
258
+ }
259
+ return true;
260
+ }
261
+
262
+ // Check if file should be stored without compression
263
+ static bool should_store(const std::string& name) {
264
+ // resources.arsc MUST be stored (Android requirement for mmap)
265
+ if (name == "resources.arsc") return true;
266
+
267
+ // Get extension
268
+ size_t dot = name.rfind('.');
269
+ if (dot == std::string::npos) return false;
270
+ std::string ext = name.substr(dot);
271
+
272
+ // Convert to lowercase for comparison (ASCII-safe)
273
+ for (auto& c : ext) {
274
+ c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
275
+ }
276
+
277
+ // Already compressed formats - no benefit from deflate
278
+ static const char* store_exts[] = {
279
+ ".png", ".jpg", ".jpeg", ".gif", ".webp", // Images (already compressed)
280
+ ".mp3", ".ogg", ".m4a", ".aac", ".flac", // Audio
281
+ ".mp4", ".webm", ".3gp", // Video
282
+ ".zip", ".jar", ".apk", // Archives
283
+ ".arsc", ".so" // Resources & native libs
284
+ };
285
+ for (const auto& e : store_exts) {
286
+ if (ext == e) return true;
287
+ }
288
+ return false;
289
+ }
290
+
291
+ void ZipWriter::add_file(const std::string& name, const std::vector<uint8_t>& data, bool compress) {
292
+ // Force STORE for certain file types
293
+ if (should_store(name)) {
294
+ add_stored(name, data);
295
+ return;
296
+ }
297
+
298
+ if (compress && data.size() > 0) {
299
+ std::vector<uint8_t> compressed = deflate_compress(data);
300
+ if (compressed.size() < data.size()) {
301
+ Entry entry;
302
+ entry.name = name;
303
+ entry.compressed_data = compressed;
304
+ entry.uncompressed_size = static_cast<uint32_t>(data.size());
305
+ entry.crc32 = calc_crc32(data.data(), data.size());
306
+ entry.compression_method = 8;
307
+ entries_.push_back(entry);
308
+ return;
309
+ }
310
+ }
311
+ add_stored(name, data);
312
+ }
313
+
314
+ void ZipWriter::add_stored(const std::string& name, const std::vector<uint8_t>& data) {
315
+ Entry entry;
316
+ entry.name = name;
317
+ entry.compressed_data = data;
318
+ entry.uncompressed_size = static_cast<uint32_t>(data.size());
319
+ entry.crc32 = calc_crc32(data.data(), data.size());
320
+ entry.compression_method = 0;
321
+ entries_.push_back(entry);
322
+ }
323
+
324
+ bool ZipWriter::save(const std::string& path) {
325
+ std::vector<uint8_t> data = finalize();
326
+
327
+ std::ofstream file;
328
+ #ifdef _WIN32
329
+ // Convert UTF-8 path to wide string for Windows
330
+ int wlen = MultiByteToWideChar(CP_UTF8, 0, path.c_str(), (int)path.size(), nullptr, 0);
331
+ if (wlen > 0) {
332
+ std::wstring wpath(wlen, 0);
333
+ MultiByteToWideChar(CP_UTF8, 0, path.c_str(), (int)path.size(), &wpath[0], wlen);
334
+ file.open(wpath, std::ios::binary);
335
+ }
336
+ #else
337
+ file.open(path, std::ios::binary);
338
+ #endif
339
+ if (!file.is_open()) return false;
340
+
341
+ file.write(reinterpret_cast<char*>(data.data()), data.size());
342
+ return true;
343
+ }
344
+
345
+ std::vector<uint8_t> ZipWriter::finalize() {
346
+ std::vector<uint8_t> output;
347
+
348
+ uint32_t offset = 0;
349
+ for (auto& entry : entries_) {
350
+ // For uncompressed (stored) files, add padding for 4-byte alignment (zipalign)
351
+ uint16_t extra_len = 0;
352
+ if (entry.compression_method == 0) {
353
+ // Calculate where data will start: offset + 30 + name_len + extra_len
354
+ uint32_t data_start = offset + 30 + static_cast<uint32_t>(entry.name.size());
355
+ uint32_t padding = (4 - (data_start % 4)) % 4;
356
+ extra_len = static_cast<uint16_t>(padding);
357
+ }
358
+
359
+ entry.local_header_offset = offset;
360
+
361
+ std::vector<uint8_t> header(30 + entry.name.size() + extra_len);
362
+ write_le<uint32_t>(&header[0], ZIP_LOCAL_FILE_HEADER_SIG);
363
+ write_le<uint16_t>(&header[4], 20);
364
+ write_le<uint16_t>(&header[6], 0);
365
+ write_le<uint16_t>(&header[8], entry.compression_method);
366
+ write_le<uint16_t>(&header[10], 0);
367
+ write_le<uint16_t>(&header[12], 0);
368
+ write_le<uint32_t>(&header[14], entry.crc32);
369
+ write_le<uint32_t>(&header[18], static_cast<uint32_t>(entry.compressed_data.size()));
370
+ write_le<uint32_t>(&header[22], entry.uncompressed_size);
371
+ write_le<uint16_t>(&header[26], static_cast<uint16_t>(entry.name.size()));
372
+ write_le<uint16_t>(&header[28], extra_len); // Extra field length for alignment
373
+ std::memcpy(&header[30], entry.name.data(), entry.name.size());
374
+ // Extra field is zero-filled for padding
375
+
376
+ output.insert(output.end(), header.begin(), header.end());
377
+ output.insert(output.end(), entry.compressed_data.begin(), entry.compressed_data.end());
378
+
379
+ offset = static_cast<uint32_t>(output.size());
380
+ }
381
+
382
+ uint32_t central_dir_offset = static_cast<uint32_t>(output.size());
383
+
384
+ for (const auto& entry : entries_) {
385
+ std::vector<uint8_t> cd_entry(46 + entry.name.size());
386
+ write_le<uint32_t>(&cd_entry[0], ZIP_CENTRAL_DIR_SIG);
387
+ write_le<uint16_t>(&cd_entry[4], 20);
388
+ write_le<uint16_t>(&cd_entry[6], 20);
389
+ write_le<uint16_t>(&cd_entry[8], 0);
390
+ write_le<uint16_t>(&cd_entry[10], entry.compression_method);
391
+ write_le<uint16_t>(&cd_entry[12], 0);
392
+ write_le<uint16_t>(&cd_entry[14], 0);
393
+ write_le<uint32_t>(&cd_entry[16], entry.crc32);
394
+ write_le<uint32_t>(&cd_entry[20], static_cast<uint32_t>(entry.compressed_data.size()));
395
+ write_le<uint32_t>(&cd_entry[24], entry.uncompressed_size);
396
+ write_le<uint16_t>(&cd_entry[28], static_cast<uint16_t>(entry.name.size()));
397
+ write_le<uint16_t>(&cd_entry[30], 0);
398
+ write_le<uint16_t>(&cd_entry[32], 0);
399
+ write_le<uint16_t>(&cd_entry[34], 0);
400
+ write_le<uint16_t>(&cd_entry[36], 0);
401
+ write_le<uint32_t>(&cd_entry[38], 0);
402
+ write_le<uint32_t>(&cd_entry[42], entry.local_header_offset);
403
+ std::memcpy(&cd_entry[46], entry.name.data(), entry.name.size());
404
+
405
+ output.insert(output.end(), cd_entry.begin(), cd_entry.end());
406
+ }
407
+
408
+ uint32_t central_dir_size = static_cast<uint32_t>(output.size()) - central_dir_offset;
409
+
410
+ std::vector<uint8_t> eocd(22);
411
+ write_le<uint32_t>(&eocd[0], ZIP_END_CENTRAL_DIR_SIG);
412
+ write_le<uint16_t>(&eocd[4], 0);
413
+ write_le<uint16_t>(&eocd[6], 0);
414
+ write_le<uint16_t>(&eocd[8], static_cast<uint16_t>(entries_.size()));
415
+ write_le<uint16_t>(&eocd[10], static_cast<uint16_t>(entries_.size()));
416
+ write_le<uint32_t>(&eocd[12], central_dir_size);
417
+ write_le<uint32_t>(&eocd[16], central_dir_offset);
418
+ write_le<uint16_t>(&eocd[20], 0);
419
+
420
+ output.insert(output.end(), eocd.begin(), eocd.end());
421
+
422
+ return output;
423
+ }
424
+
425
+ } // namespace apk