diff --git a/.gitmodules b/.gitmodules index 92ffedc27..91dbcac23 100644 --- a/.gitmodules +++ b/.gitmodules @@ -40,3 +40,6 @@ [submodule "termux-elf-cleaner"] path = tools/termux-elf-cleaner url = https://github.com/termux/termux-elf-cleaner.git +[submodule "zopfli"] + path = native/jni/external/zopfli + url = https://android.googlesource.com/platform/external/zopfli diff --git a/native/jni/Android.mk b/native/jni/Android.mk index c58e88345..99c662280 100644 --- a/native/jni/Android.mk +++ b/native/jni/Android.mk @@ -78,7 +78,7 @@ ifdef B_BOOT include $(CLEAR_VARS) LOCAL_MODULE := magiskboot -LOCAL_STATIC_LIBRARIES := libmincrypt liblzma liblz4 libbz2 libfdt libutils libz +LOCAL_STATIC_LIBRARIES := libmincrypt liblzma liblz4 libbz2 libfdt libutils libz libzopfli LOCAL_C_INCLUDES := jni/include LOCAL_SRC_FILES := \ diff --git a/native/jni/external/Android.mk b/native/jni/external/Android.mk index 98c00c967..3930ff29a 100644 --- a/native/jni/external/Android.mk +++ b/native/jni/external/Android.mk @@ -402,6 +402,27 @@ LOCAL_SRC_FILES := \ zlib/zutil.c include $(BUILD_STATIC_LIBRARY) +# libzopfli.a +include $(CLEAR_VARS) +LOCAL_MODULE:= libzopfli +LOCAL_C_INCLUDES := $(LOCAL_PATH)/zopfli/src +LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES) +LOCAL_CFLAGS := -O2 -Wall -Werror -Wno-unused -Wno-unused-parameter +LOCAL_SRC_FILES := \ + zopfli/src/zopfli/blocksplitter.c \ + zopfli/src/zopfli/cache.c \ + zopfli/src/zopfli/deflate.c \ + zopfli/src/zopfli/gzip_container.c \ + zopfli/src/zopfli/hash.c \ + zopfli/src/zopfli/katajainen.c \ + zopfli/src/zopfli/lz77.c \ + zopfli/src/zopfli/squeeze.c \ + zopfli/src/zopfli/tree.c \ + zopfli/src/zopfli/util.c \ + zopfli/src/zopfli/zlib_container.c \ + zopfli/src/zopfli/zopfli_lib.c +include $(BUILD_STATIC_LIBRARY) + CWD := $(LOCAL_PATH) include $(CWD)/systemproperties/Android.mk include $(CWD)/mincrypt/Android.mk diff --git a/native/jni/external/zopfli b/native/jni/external/zopfli new file mode 160000 index 000000000..7809db48d --- /dev/null +++ b/native/jni/external/zopfli @@ -0,0 +1 @@ +Subproject commit 7809db48d831bdba8c0beed0b9fedb5d88a17de0 diff --git a/native/jni/magiskboot/compress.cpp b/native/jni/magiskboot/compress.cpp index 1b7344cdd..d1b19775d 100644 --- a/native/jni/magiskboot/compress.cpp +++ b/native/jni/magiskboot/compress.cpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include @@ -21,6 +23,12 @@ constexpr size_t CHUNK = 0x40000; constexpr size_t LZ4_UNCOMPRESSED = 0x800000; constexpr size_t LZ4_COMPRESSED = LZ4_COMPRESSBOUND(LZ4_UNCOMPRESSED); +#if defined(ZOPFLI_MASTER_BLOCK_SIZE) && ZOPFLI_MASTER_BLOCK_SIZE > 0 +constexpr size_t ZOPFLI_CHUNK = ZOPFLI_MASTER_BLOCK_SIZE; +#else +constexpr size_t ZOPFLI_CHUNK = CHUNK; +#endif + class cpr_stream : public filter_stream { public: using filter_stream::filter_stream; @@ -106,6 +114,129 @@ public: explicit gz_encoder(stream_ptr &&base) : gz_strm(ENCODE, std::move(base)) {}; }; +class zopfli_gz_strm : public cpr_stream { +public: + int write(const void *buf, size_t len) override { + return len ? write(buf, len, 0) : 0; + } + + ~zopfli_gz_strm() override { + switch(mode) { + case ENCODE: + write(nullptr, 0, 1); + break; + } + } + +protected: + enum mode_t { + ENCODE + } mode; + + ZopfliOptions zo; + + zopfli_gz_strm(mode_t mode, stream_ptr &&base) : + cpr_stream(std::move(base)), mode(mode), out(nullptr), outsize(0), bp(0), crcvalue(0xffffffffu), in_read(0) { + switch(mode) { + case ENCODE: + out = 0; + outsize = 0; + bp = 0; + crcvalue = crc32_z(0L, Z_NULL, 0); + + ZopfliInitOptions(&zo); + + // Speed things up a bit, this still leads to better compression than zlib + zo.numiterations = 1; + zo.blocksplitting = 0; + + ZOPFLI_APPEND_DATA(31, &out, &outsize); /* ID1 */ + ZOPFLI_APPEND_DATA(139, &out, &outsize); /* ID2 */ + ZOPFLI_APPEND_DATA(8, &out, &outsize); /* CM */ + ZOPFLI_APPEND_DATA(0, &out, &outsize); /* FLG */ + /* MTIME */ + ZOPFLI_APPEND_DATA(0, &out, &outsize); + ZOPFLI_APPEND_DATA(0, &out, &outsize); + ZOPFLI_APPEND_DATA(0, &out, &outsize); + ZOPFLI_APPEND_DATA(0, &out, &outsize); + + ZOPFLI_APPEND_DATA(2, &out, &outsize); /* XFL, 2 indicates best compression. */ + ZOPFLI_APPEND_DATA(3, &out, &outsize); /* OS follows Unix conventions. */ + break; + } + } + +private: + unsigned char* out = nullptr; + size_t outsize = 0; + unsigned char bp = 0; + unsigned long crcvalue = 0xffffffffu; + uint32_t in_read = 0; + + int write(const void *buf, size_t len, int flush) { + int ret = 0; + switch(mode) { + case ENCODE: + in_read += len; + if (len) + crcvalue = crc32_z(crcvalue, (Bytef *)buf, len); + if (flush) { + ZopfliDeflate(&zo, 2, 1, (const unsigned char *)buf, len, &bp, &out, &outsize); + + /* CRC */ + ZOPFLI_APPEND_DATA(crcvalue % 256, &out, &outsize); + ZOPFLI_APPEND_DATA((crcvalue >> 8) % 256, &out, &outsize); + ZOPFLI_APPEND_DATA((crcvalue >> 16) % 256, &out, &outsize); + ZOPFLI_APPEND_DATA((crcvalue >> 24) % 256, &out, &outsize); + + /* ISIZE */ + ZOPFLI_APPEND_DATA(in_read % 256, &out, &outsize); + ZOPFLI_APPEND_DATA((in_read >> 8) % 256, &out, &outsize); + ZOPFLI_APPEND_DATA((in_read >> 16) % 256, &out, &outsize); + ZOPFLI_APPEND_DATA((in_read >> 24) % 256, &out, &outsize); + ret += bwrite(out, outsize); + free(out); + out = nullptr; + bp = 0; + outsize = 0; + } + else { + for(size_t offset = 0; offset < len; offset += ZOPFLI_CHUNK) { + ZopfliDeflatePart(&zo, 2, 0, (const unsigned char *)buf, offset, offset + ((len - offset) < ZOPFLI_CHUNK ? len - offset : ZOPFLI_CHUNK), &bp, &out, &outsize); + bp &= 7; + if (bp & 1) { + if (bp == 7) + ZOPFLI_APPEND_DATA(0, &out, &outsize); + ZOPFLI_APPEND_DATA(0, &out, &outsize); + ZOPFLI_APPEND_DATA(0, &out, &outsize); + ZOPFLI_APPEND_DATA(0xff, &out, &outsize); + ZOPFLI_APPEND_DATA(0xff, &out, &outsize); + } + else if (bp) { + do { + out[outsize - 1] += 2 << bp; + ZOPFLI_APPEND_DATA(0, &out, &outsize); + bp += 2; + } while (bp < 8); + } + + ret += bwrite(out, outsize); + free(out); + out = nullptr; + bp = 0; + outsize = 0; + } + } + return ret; + } + } +}; + +class zopfli_gz_encoder : public zopfli_gz_strm { +public: + explicit zopfli_gz_encoder(stream_ptr &&base) : zopfli_gz_strm(ENCODE, std::move(base)) {}; +}; + class bz_strm : public cpr_stream { public: ssize_t write(const void *buf, size_t len) override { @@ -535,6 +666,7 @@ stream_ptr get_encoder(format_t type, stream_ptr &&base) { case LZ4_LG: return make_unique(std::move(base), true); case GZIP: + return make_unique(std::move(base)); default: return make_unique(std::move(base)); }