Upload Apkdiff Module Source Code
This commit is contained in:
42
apkdiff/gen/CMakeLists.txt
Normal file
42
apkdiff/gen/CMakeLists.txt
Normal file
@@ -0,0 +1,42 @@
|
||||
cmake_minimum_required(VERSION 3.15)
|
||||
project(apkdiff_gen C)
|
||||
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
|
||||
set(BUNDLED_ZLIB_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/zlib")
|
||||
|
||||
message(STATUS "Using bundled zlib from ${BUNDLED_ZLIB_DIR}")
|
||||
add_subdirectory("${BUNDLED_ZLIB_DIR}" "${CMAKE_BINARY_DIR}/zlib" EXCLUDE_FROM_ALL)
|
||||
set(ZLIB_LIB zlibstatic)
|
||||
set(ZLIB_INC "${BUNDLED_ZLIB_DIR}")
|
||||
|
||||
set(MINIZIP_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/zlib/contrib/minizip")
|
||||
|
||||
add_library(minizip STATIC
|
||||
"${MINIZIP_DIR}/ioapi.c"
|
||||
"${MINIZIP_DIR}/unzip.c"
|
||||
"${MINIZIP_DIR}/zip.c"
|
||||
)
|
||||
target_include_directories(minizip PUBLIC "${MINIZIP_DIR}" "${ZLIB_INC}")
|
||||
target_compile_definitions(minizip PRIVATE
|
||||
NOCRYPT NOUNCRYPT
|
||||
$<$<C_COMPILER_ID:MSVC>:_CRT_SECURE_NO_WARNINGS;USE_FILE32API>
|
||||
)
|
||||
|
||||
add_executable(gen
|
||||
main.c
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/../common/zlib_stream.c"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/../bsdiff.c"
|
||||
)
|
||||
|
||||
target_include_directories(gen PRIVATE
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/.."
|
||||
"${MINIZIP_DIR}"
|
||||
"${ZLIB_INC}"
|
||||
)
|
||||
|
||||
target_link_libraries(gen PRIVATE minizip ${ZLIB_LIB})
|
||||
|
||||
target_compile_definitions(gen PRIVATE
|
||||
$<$<C_COMPILER_ID:MSVC>:_CRT_SECURE_NO_WARNINGS>
|
||||
)
|
||||
356
apkdiff/gen/main.c
Normal file
356
apkdiff/gen/main.c
Normal file
@@ -0,0 +1,356 @@
|
||||
/*
|
||||
* gen/main.c - APK patch generation tool
|
||||
*
|
||||
* Usage: gen.exe <oldApk> <newApk> <output>
|
||||
*
|
||||
* Reads two APK (ZIP) files, compares their entries, and generates
|
||||
* a binary patch file that can be applied by the JNI bspatch module.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "unzip.h"
|
||||
#include "bsdiff.h"
|
||||
#include "common/patch_format.h"
|
||||
#include "common/zlib_stream.h"
|
||||
|
||||
/* ---- ZIP entry storage ---- */
|
||||
|
||||
typedef struct {
|
||||
char* path;
|
||||
uint8_t* data;
|
||||
size_t size;
|
||||
uLong crc;
|
||||
} file_entry;
|
||||
|
||||
typedef struct {
|
||||
file_entry* entries;
|
||||
int count;
|
||||
int capacity;
|
||||
} file_set;
|
||||
|
||||
static void file_set_init(file_set* set)
|
||||
{
|
||||
set->entries = NULL;
|
||||
set->count = 0;
|
||||
set->capacity = 0;
|
||||
}
|
||||
|
||||
static void file_set_free(file_set* set)
|
||||
{
|
||||
for (int i = 0; i < set->count; i++) {
|
||||
free(set->entries[i].path);
|
||||
free(set->entries[i].data);
|
||||
}
|
||||
free(set->entries);
|
||||
set->entries = NULL;
|
||||
set->count = 0;
|
||||
}
|
||||
|
||||
static void file_set_add(file_set* set, const char* path, uint8_t* data, size_t size, uLong crc)
|
||||
{
|
||||
if (set->count >= set->capacity) {
|
||||
set->capacity = set->capacity ? set->capacity * 2 : 256;
|
||||
set->entries = (file_entry*)realloc(set->entries, set->capacity * sizeof(file_entry));
|
||||
}
|
||||
set->entries[set->count].path = strdup(path);
|
||||
set->entries[set->count].data = data;
|
||||
set->entries[set->count].size = size;
|
||||
set->entries[set->count].crc = crc;
|
||||
set->count++;
|
||||
}
|
||||
|
||||
static int path_cmp(const void* a, const void* b)
|
||||
{
|
||||
return strcmp(((const file_entry*)a)->path, ((const file_entry*)b)->path);
|
||||
}
|
||||
|
||||
static void file_set_sort(file_set* set)
|
||||
{
|
||||
if (set->count > 0)
|
||||
qsort(set->entries, set->count, sizeof(file_entry), path_cmp);
|
||||
}
|
||||
|
||||
/* Binary search by path (set must be sorted) */
|
||||
static file_entry* file_set_find(file_set* set, const char* path)
|
||||
{
|
||||
int lo = 0, hi = set->count - 1;
|
||||
while (lo <= hi) {
|
||||
int mid = (lo + hi) / 2;
|
||||
int cmp = strcmp(set->entries[mid].path, path);
|
||||
if (cmp < 0) lo = mid + 1;
|
||||
else if (cmp > 0) hi = mid - 1;
|
||||
else return &set->entries[mid];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ---- Read all entries from a ZIP/APK into a file_set ---- */
|
||||
|
||||
static int read_zip_entries(const char* path, file_set* set)
|
||||
{
|
||||
unzFile zf = unzOpen(path);
|
||||
if (!zf) {
|
||||
fprintf(stderr, "Error: cannot open %s\n", path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
unz_global_info gi;
|
||||
if (unzGetGlobalInfo(zf, &gi) != UNZ_OK) {
|
||||
fprintf(stderr, "Error: unzGetGlobalInfo failed\n");
|
||||
unzClose(zf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (unzGoToFirstFile(zf) != UNZ_OK) {
|
||||
unzClose(zf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
do {
|
||||
unz_file_info fi;
|
||||
char filename[1024];
|
||||
|
||||
if (unzGetCurrentFileInfo(zf, &fi, filename, sizeof(filename),
|
||||
NULL, 0, NULL, 0) != UNZ_OK)
|
||||
continue;
|
||||
|
||||
/* Skip directories */
|
||||
size_t len = strlen(filename);
|
||||
if (len > 0 && filename[len - 1] == '/')
|
||||
continue;
|
||||
|
||||
if (unzOpenCurrentFile(zf) != UNZ_OK)
|
||||
continue;
|
||||
|
||||
uint8_t* data = NULL;
|
||||
if (fi.uncompressed_size > 0) {
|
||||
data = (uint8_t*)malloc(fi.uncompressed_size + 1);
|
||||
if (!data) {
|
||||
unzCloseCurrentFile(zf);
|
||||
continue;
|
||||
}
|
||||
int read_bytes = unzReadCurrentFile(zf, data, (unsigned)fi.uncompressed_size);
|
||||
if (read_bytes != (int)fi.uncompressed_size) {
|
||||
fprintf(stderr, "Warning: short read on %s (%d/%lu)\n",
|
||||
filename, read_bytes, fi.uncompressed_size);
|
||||
free(data);
|
||||
unzCloseCurrentFile(zf);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
unzCloseCurrentFile(zf);
|
||||
file_set_add(set, filename, data, fi.uncompressed_size, fi.crc);
|
||||
|
||||
} while (unzGoToNextFile(zf) == UNZ_OK);
|
||||
|
||||
unzClose(zf);
|
||||
file_set_sort(set);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- zlib compress a buffer, returns malloc'd buffer and sets *out_size ---- */
|
||||
|
||||
static uint8_t* zlib_compress_buffer(const uint8_t* data, size_t in_size, uint32_t* out_size)
|
||||
{
|
||||
uLongf dest_len = compressBound((uLong)in_size);
|
||||
uint8_t* dest = (uint8_t*)malloc(dest_len);
|
||||
if (!dest) return NULL;
|
||||
|
||||
if (compress2(dest, &dest_len, data, (uLong)in_size, Z_BEST_COMPRESSION) != Z_OK) {
|
||||
free(dest);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*out_size = (uint32_t)dest_len;
|
||||
return dest;
|
||||
}
|
||||
|
||||
/* ---- Write a COPY entry (unchanged file) ---- */
|
||||
|
||||
static int write_copy_entry(FILE* patch, const char* path)
|
||||
{
|
||||
return patch_write_entry_header(patch, path,
|
||||
ENTRY_TYPE_COPY, 0, 0, 0);
|
||||
}
|
||||
|
||||
/* ---- Write a NEW entry (file only in new APK) ---- */
|
||||
|
||||
static int write_new_entry(FILE* patch, const char* path,
|
||||
const uint8_t* data, size_t size)
|
||||
{
|
||||
uint32_t comp_size = 0;
|
||||
uint8_t* comp_data = zlib_compress_buffer(data, size, &comp_size);
|
||||
if (!comp_data) return -1;
|
||||
|
||||
int ret = patch_write_entry_header(patch, path,
|
||||
ENTRY_TYPE_NEW, 0, (int64_t)size, comp_size);
|
||||
if (ret == 0) {
|
||||
if (fwrite(comp_data, 1, comp_size, patch) != comp_size)
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
free(comp_data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* ---- Write a PATCH entry (modified file, bsdiff + zlib) ---- */
|
||||
|
||||
static int write_patch_entry(FILE* patch, const char* path,
|
||||
const uint8_t* old_data, size_t old_size,
|
||||
const uint8_t* new_data, size_t new_size)
|
||||
{
|
||||
/* Use tmpfile to capture bsdiff output via zlib write callbacks */
|
||||
FILE* tmp = tmpfile();
|
||||
if (!tmp) {
|
||||
fprintf(stderr, "Error: tmpfile() failed\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
zlib_write_ctx wctx;
|
||||
struct bsdiff_stream stream;
|
||||
zlib_setup_bsdiff_stream(&stream, &wctx, tmp);
|
||||
|
||||
int bsdiff_ret = bsdiff(old_data, (int64_t)old_size,
|
||||
new_data, (int64_t)new_size, &stream);
|
||||
zlib_write_finish(&wctx);
|
||||
|
||||
if (bsdiff_ret != 0) {
|
||||
fprintf(stderr, "Error: bsdiff failed for %s\n", path);
|
||||
fclose(tmp);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Read compressed patch data from temp file */
|
||||
long comp_size = ftell(tmp);
|
||||
if (comp_size < 0) {
|
||||
fclose(tmp);
|
||||
return -1;
|
||||
}
|
||||
|
||||
fseek(tmp, 0, SEEK_SET);
|
||||
uint8_t* comp_data = (uint8_t*)malloc(comp_size);
|
||||
if (!comp_data) {
|
||||
fclose(tmp);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((long)fread(comp_data, 1, comp_size, tmp) != comp_size) {
|
||||
free(comp_data);
|
||||
fclose(tmp);
|
||||
return -1;
|
||||
}
|
||||
fclose(tmp);
|
||||
|
||||
/* Write entry header + compressed patch data */
|
||||
int ret = patch_write_entry_header(patch, path,
|
||||
ENTRY_TYPE_PATCH, (int64_t)old_size, (int64_t)new_size, (uint32_t)comp_size);
|
||||
if (ret == 0) {
|
||||
if (fwrite(comp_data, 1, comp_size, patch) != (size_t)comp_size)
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
free(comp_data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* ---- main ---- */
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
if (argc != 4) {
|
||||
fprintf(stderr, "Usage: %s <oldApk> <newApk> <output>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char* old_path = argv[1];
|
||||
const char* new_path = argv[2];
|
||||
const char* out_path = argv[3];
|
||||
|
||||
/* Read both APKs */
|
||||
file_set old_set, new_set;
|
||||
file_set_init(&old_set);
|
||||
file_set_init(&new_set);
|
||||
|
||||
printf("Reading old APK: %s\n", old_path);
|
||||
if (read_zip_entries(old_path, &old_set) != 0)
|
||||
return 1;
|
||||
printf(" %d entries\n", old_set.count);
|
||||
|
||||
printf("Reading new APK: %s\n", new_path);
|
||||
if (read_zip_entries(new_path, &new_set) != 0) {
|
||||
file_set_free(&old_set);
|
||||
return 1;
|
||||
}
|
||||
printf(" %d entries\n", new_set.count);
|
||||
|
||||
/* Open output patch file */
|
||||
FILE* patch = fopen(out_path, "wb");
|
||||
if (!patch) {
|
||||
fprintf(stderr, "Error: cannot create %s\n", out_path);
|
||||
file_set_free(&old_set);
|
||||
file_set_free(&new_set);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Write placeholder header (entry_count will be filled later) */
|
||||
patch_write_header(patch, 0);
|
||||
|
||||
uint32_t entry_count = 0;
|
||||
int stats_copy = 0, stats_patch = 0, stats_new = 0;
|
||||
|
||||
/* Process all entries in the new APK */
|
||||
for (int i = 0; i < new_set.count; i++) {
|
||||
file_entry* ne = &new_set.entries[i];
|
||||
file_entry* oe = file_set_find(&old_set, ne->path);
|
||||
|
||||
if (!oe) {
|
||||
/* New file - only in new APK */
|
||||
printf(" NEW %s (%zu bytes)\n", ne->path, ne->size);
|
||||
if (write_new_entry(patch, ne->path, ne->data, ne->size) != 0) {
|
||||
fprintf(stderr, "Error writing NEW entry: %s\n", ne->path);
|
||||
goto cleanup;
|
||||
}
|
||||
entry_count++;
|
||||
stats_new++;
|
||||
}
|
||||
else if (oe->size == ne->size && oe->crc == ne->crc) {
|
||||
/* Unchanged - mark as COPY */
|
||||
if (write_copy_entry(patch, ne->path) != 0) {
|
||||
fprintf(stderr, "Error writing COPY entry: %s\n", ne->path);
|
||||
goto cleanup;
|
||||
}
|
||||
entry_count++;
|
||||
stats_copy++;
|
||||
}
|
||||
else {
|
||||
/* Modified - generate bsdiff patch */
|
||||
printf(" PATCH %s (%zu -> %zu bytes)\n", ne->path, oe->size, ne->size);
|
||||
if (write_patch_entry(patch, ne->path,
|
||||
oe->data, oe->size,
|
||||
ne->data, ne->size) != 0) {
|
||||
fprintf(stderr, "Error writing PATCH entry: %s\n", ne->path);
|
||||
goto cleanup;
|
||||
}
|
||||
entry_count++;
|
||||
stats_patch++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Seek back and write actual entry count */
|
||||
fseek(patch, (long)APKDIFF_MAGIC_SIZE, SEEK_SET);
|
||||
fwrite(&entry_count, sizeof(uint32_t), 1, patch);
|
||||
|
||||
printf("\nDone! Patch: %s\n", out_path);
|
||||
printf(" Entries: %u total (%d copy, %d patch, %d new)\n",
|
||||
entry_count, stats_copy, stats_patch, stats_new);
|
||||
|
||||
cleanup:
|
||||
fclose(patch);
|
||||
file_set_free(&old_set);
|
||||
file_set_free(&new_set);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user