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.
- package/android/build.gradle +22 -0
- package/android/src/main/cpp/CMakeLists.txt +57 -0
- package/android/src/main/cpp/apk/apk_handler.cpp +121 -0
- package/android/src/main/cpp/apk/zip_utils.cpp +425 -0
- package/android/src/main/cpp/arsc/arsc_parser.cpp +390 -0
- package/android/src/main/cpp/dex/dex_builder.cpp +752 -0
- package/android/src/main/cpp/dex/dex_parser.cpp +620 -0
- package/android/src/main/cpp/dex/smali_disasm.cpp +1223 -0
- package/android/src/main/cpp/dex/smali_to_java.cpp +576 -0
- package/android/src/main/cpp/include/apk/apk_handler.h +41 -0
- package/android/src/main/cpp/include/apk/zip_utils.h +57 -0
- package/android/src/main/cpp/include/arsc/arsc_parser.h +98 -0
- package/android/src/main/cpp/include/dex/dex_builder.h +189 -0
- package/android/src/main/cpp/include/dex/dex_parser.h +137 -0
- package/android/src/main/cpp/include/dex/smali_disasm.h +127 -0
- package/android/src/main/cpp/include/dex/smali_to_java.h +50 -0
- package/android/src/main/cpp/include/xml/android_resources.h +495 -0
- package/android/src/main/cpp/include/xml/axml_parser.h +147 -0
- package/android/src/main/cpp/jni_bridge.cpp +872 -0
- package/android/src/main/cpp/third_party/miniz.c +646 -0
- package/android/src/main/cpp/third_party/miniz.h +605 -0
- package/android/src/main/cpp/third_party/miniz_common.h +97 -0
- package/android/src/main/cpp/third_party/miniz_export.h +6 -0
- package/android/src/main/cpp/third_party/miniz_tdef.c +1597 -0
- package/android/src/main/cpp/third_party/miniz_tdef.h +199 -0
- package/android/src/main/cpp/third_party/miniz_tinfl.c +770 -0
- package/android/src/main/cpp/third_party/miniz_tinfl.h +150 -0
- package/android/src/main/cpp/third_party/miniz_zip.c +4895 -0
- package/android/src/main/cpp/third_party/miniz_zip.h +454 -0
- package/android/src/main/cpp/third_party/nlohmann_json/CMakeLists.txt +0 -0
- package/android/src/main/cpp/third_party/nlohmann_json/single_include/nlohmann/json.hpp +24765 -0
- package/android/src/main/cpp/xml/axml_parser.cpp +1701 -0
- package/android/src/main/java/com/aetherlink/dexeditor/CppDex.java +295 -0
- package/android/src/main/java/com/aetherlink/dexeditor/DexManager.java +20 -20
- package/package.json +1 -1
- package/android/src/main/java/com/aetherlink/dexeditor/RustDex.java +0 -203
- package/android/src/main/jniLibs/arm64-v8a/libdex_rust.so +0 -0
- package/android/src/main/jniLibs/armeabi-v7a/libdex_rust.so +0 -0
- package/android/src/main/jniLibs/x86/libdex_rust.so +0 -0
- package/android/src/main/jniLibs/x86_64/libdex_rust.so +0 -0
package/android/build.gradle
CHANGED
|
@@ -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
|