From deae08fc4b2d3ec35ac1ec9883a90f6a26b35bc0 Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Sat, 27 Jan 2018 08:25:34 +0800 Subject: [PATCH] Port zipadjust to Java --- README.MD | 14 +- app | 2 +- build.py | 39 +-- crypto/build.gradle | 2 +- .../java/com/topjohnwu/crypto/SignAPK.java | 24 ++ .../java/com/topjohnwu/crypto/ZipAdjust.java | 282 +++++++++++++++ .../java/com/topjohnwu/crypto/ZipSigner.java | 13 +- ziptools/zipadjust.exe | Bin 74072 -> 0 bytes ziptools/zipadjust_src/zipadjust.c | 320 ------------------ ziptools/zipadjust_src/zipadjust.h | 27 -- ziptools/zipadjust_src/zipadjust_run.c | 45 --- 11 files changed, 321 insertions(+), 447 deletions(-) create mode 100644 crypto/src/main/java/com/topjohnwu/crypto/ZipAdjust.java delete mode 100644 ziptools/zipadjust.exe delete mode 100644 ziptools/zipadjust_src/zipadjust.c delete mode 100644 ziptools/zipadjust_src/zipadjust.h delete mode 100644 ziptools/zipadjust_src/zipadjust_run.c diff --git a/README.MD b/README.MD index 4adaa19ea..b545ac8fe 100644 --- a/README.MD +++ b/README.MD @@ -6,17 +6,15 @@ ### Environment Requirements -1. A 64-bit machine: `cmake` for Android is only available in 64-bit -2. Python 3.5+: run `build.py` script -3. Java Development Kit (JDK) 8: Compile Magisk Manager and sign zips -4. Latest Android SDK: `ANDROID_HOME` environment variable should point to the Android SDK folder -5. Android NDK: Install NDK along with SDK (`$ANDROID_HOME/ndk-bundle`), or specify custom path `ANDROID_NDK` -6. (Windows Only) Python package Colorama: Install with `pip install colorama`, used for ANSI color codes -7. (Unix only) C compiler: Build `zipadjust`. Windows users can use the pre-built `zipadjust.exe` +1. Python 3.5+: run `build.py` script +2. Java Development Kit (JDK) 8: Compile Magisk Manager and sign zips +3. Latest Android SDK: `ANDROID_HOME` environment variable should point to the Android SDK folder +4. Android NDK: Install NDK along with SDK (`$ANDROID_HOME/ndk-bundle`), or specify custom path `ANDROID_NDK` +5. (Windows Only) Python package Colorama: Install with `pip install colorama`, used for ANSI color codes ### Instructions and Notes 1. Magisk can be built with the latest NDK (r16 as of writing), however binaries released officially will be built with NDK r10e due to ELF incompatibilities with the binaries built from the newer NDKs. -2. The easiest way to setup the environment is by importing this folder as an Android Studio project. The IDE will download required components and construct the environment for you. You still have to set the `ANDROID_HOME` environment variable to point to the SDK path. +2. The easiest way to setup the environment is by importing the folder as an Android Studio project. The IDE will download required components and construct the environment for you. You still have to set the `ANDROID_HOME` environment variable to point to the SDK path. 3. Run `build.py` with argument `-h` to see the built-in help message. The `-h` option also works for each supported actions, e.g. `./build.py binary -h` 4. Build everything with `build.py`, don't directly call `gradlew` or `ndk-build`, since most requires special setup / dependencies. 5. By default, `build.py` will build binaries and Magisk Manager in debug mode. If you want to build Magisk Manager in release mode (through the flag `--release`), you will need to place a Java Keystore file at `release_signature.jks` to sign Magisk Manager's APK. For more information, check out [Google's Official Documentation](https://developer.android.com/studio/publish/app-signing.html#signing-manually). diff --git a/app b/app index f37f33067..19af5f9e0 160000 --- a/app +++ b/app @@ -1 +1 @@ -Subproject commit f37f330670b1dba2cc76d52d07587c7c605e096a +Subproject commit 19af5f9e0be9e725264d85552fd60f46bffff32d diff --git a/build.py b/build.py index 201c8bb74..3e05067c9 100755 --- a/build.py +++ b/build.py @@ -26,13 +26,6 @@ try: except FileNotFoundError: error('Please install Java and make sure \'java\' is available in PATH') -# If not Windows, we need gcc to compile -if os.name != 'nt': - try: - subprocess.run(['gcc', '-v'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - except FileNotFoundError: - error('Please install C compiler and make sure \'gcc\' is available in PATH') - import argparse import multiprocessing import zipfile @@ -326,46 +319,22 @@ def zip_uninstaller(args): sign_adjust_zip(unsigned, output) def sign_adjust_zip(unsigned, output): - signer_name = 'zipsigner-2.0.jar' + signer_name = 'zipsigner-2.1.jar' jarsigner = os.path.join('crypto', 'build', 'libs', signer_name) - if os.name != 'nt' and not os.path.exists(os.path.join('ziptools', 'zipadjust')): - header('* Building zipadjust') - # Compile zipadjust - proc = subprocess.run('gcc -o ziptools/zipadjust ziptools/zipadjust_src/*.c -lz', shell=True) - if proc.returncode != 0: - error('Build zipadjust failed!') if not os.path.exists(jarsigner): header('* Building ' + signer_name) proc = subprocess.run('{} crypto:shadowJar'.format(os.path.join('.', 'gradlew')), shell=True) if proc.returncode != 0: error('Build {} failed!'.format(signer_name)) - header('* Signing / Adjusting Zip') + header('* Signing Zip') signed = tempfile.mkstemp()[1] - # Unsigned->signed - proc = subprocess.run(['java', '-jar', jarsigner, unsigned, signed]) + proc = subprocess.run(['java', '-jar', jarsigner, unsigned, output]) if proc.returncode != 0: - error('First sign flashable zip failed!') - - adjusted = tempfile.mkstemp()[1] - - # Adjust zip - proc = subprocess.run([os.path.join('ziptools', 'zipadjust'), signed, adjusted]) - if proc.returncode != 0: - error('Adjust flashable zip failed!') - - # Adjusted -> output - proc = subprocess.run(['java', '-jar', jarsigner, "-m", adjusted, output]) - if proc.returncode != 0: - error('Second sign flashable zip failed!') - - # Cleanup - rm(unsigned) - rm(signed) - rm(adjusted) + error('Signing zip failed!') def cleanup(args): if len(args.target) == 0: diff --git a/crypto/build.gradle b/crypto/build.gradle index 5edf0b6d1..7c09cc5be 100644 --- a/crypto/build.gradle +++ b/crypto/build.gradle @@ -15,7 +15,7 @@ jar { shadowJar { baseName = 'zipsigner' classifier = null - version = 2.0 + version = 2.1 } buildscript { diff --git a/crypto/src/main/java/com/topjohnwu/crypto/SignAPK.java b/crypto/src/main/java/com/topjohnwu/crypto/SignAPK.java index 44f798f89..e6a33dd16 100644 --- a/crypto/src/main/java/com/topjohnwu/crypto/SignAPK.java +++ b/crypto/src/main/java/com/topjohnwu/crypto/SignAPK.java @@ -18,9 +18,13 @@ import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; import org.bouncycastle.util.encoders.Base64; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileInputStream; import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.FilterOutputStream; import java.io.IOException; import java.io.InputStream; @@ -67,6 +71,26 @@ public class SignAPK { Security.insertProviderAt(sBouncyCastleProvider, 1); } + public static void signZip(InputStream cert, InputStream key, + JarMap input, OutputStream output) throws Exception { + File temp1 = File.createTempFile("signAPK", null); + File temp2 = File.createTempFile("signAPK", null); + try { + try (OutputStream out = new BufferedOutputStream(new FileOutputStream(temp1))) { + signZip(cert, key, input, out, false); + } + + ZipAdjust.adjust(temp1, temp2); + + try (JarMap map = new JarMap(temp2, false)) { + signZip(cert, key, map, output, true); + } + } finally { + temp1.delete(); + temp2.delete(); + } + } + public static void signZip(InputStream cert, InputStream key, JarMap input, OutputStream output, boolean minSign) throws Exception { int alignment = 4; diff --git a/crypto/src/main/java/com/topjohnwu/crypto/ZipAdjust.java b/crypto/src/main/java/com/topjohnwu/crypto/ZipAdjust.java new file mode 100644 index 000000000..72e07143a --- /dev/null +++ b/crypto/src/main/java/com/topjohnwu/crypto/ZipAdjust.java @@ -0,0 +1,282 @@ +package com.topjohnwu.crypto; + +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel; +import java.util.Arrays; + +public class ZipAdjust { + + public static void adjust(File input, File output) throws IOException { + try ( + RandomAccessFile in = new RandomAccessFile(input, "r"); + FileOutputStream out = new FileOutputStream(output) + ) { + adjust(in, out); + } + } + + public static void adjust(RandomAccessFile in, OutputStream out) throws IOException { + + CentralFooter footer = new CentralFooter(in); + int outOff = 0; + long centralOff = unsigned(footer.centralDirectoryOffset); + CentralHeader[] centralHeaders = new CentralHeader[unsigned(footer.numEntries)]; + + // Loop through central directory entries + for (int i = 0; i < centralHeaders.length; ++i) { + // Read central header + in.seek(centralOff); + centralHeaders[i] = new CentralHeader(in); + centralOff = in.getFilePointer(); + + // Read local header + in.seek(unsigned(centralHeaders[i].localHeaderOffset)); + LocalHeader localHeader = new LocalHeader(in); + + // Make sure local and central headers matches, and strip out data descriptor flag + centralHeaders[i].localHeaderOffset = outOff; + centralHeaders[i].flags &= ~(1 << 3); + localHeader.flags = centralHeaders[i].flags; + localHeader.crc32 = centralHeaders[i].crc32; + localHeader.compressedSize = centralHeaders[i].compressedSize; + localHeader.uncompressedSize = centralHeaders[i].uncompressedSize; + localHeader.fileNameLength = centralHeaders[i].fileNameLength; + localHeader.filename = centralHeaders[i].filename; + + // Write local header + outOff += localHeader.write(out); + + // Copy data + int read; + long len = unsigned(localHeader.compressedSize); + outOff += len; + byte data[] = new byte[4096]; + while ((read = in.read(data, 0, + len < data.length ? (int) len : data.length)) > 0) { + out.write(data, 0, read); + len -= read; + } + } + + footer.centralDirectoryOffset = outOff; + + // Write central directory + outOff = 0; + for (CentralHeader header : centralHeaders) + outOff += header.write(out); + + // Write central directory record + footer.centralDirectorySize = outOff; + footer.write(out); + } + + public static short unsigned(byte n) { + return (short)(n & 0xff); + } + + public static int unsigned(short n) { + return n & 0xffff; + } + + public static long unsigned(int n) { + return n & 0xffffffffL; + } + + public static class CentralFooter { + + static final int MAGIC = 0x06054b50; + + int signature; + short diskNumber; + short centralDirectoryDiskNumber; + short numEntriesThisDisk; + short numEntries; + int centralDirectorySize; + int centralDirectoryOffset; + short zipCommentLength; + // byte[] comments; + + CentralFooter(RandomAccessFile file) throws IOException { + byte[] buffer = new byte[22]; + for (long i = file.length() - 4; i >= 0; --i) { + file.seek(i); + file.read(buffer, 0 ,4); + ByteBuffer buf = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN); + signature = buf.getInt(); + if (signature != MAGIC) { + continue; + } + file.read(buffer, 4, buffer.length - 4); + diskNumber = buf.getShort(); + centralDirectoryDiskNumber = buf.getShort(); + numEntriesThisDisk = buf.getShort(); + numEntries = buf.getShort(); + centralDirectorySize = buf.getInt(); + centralDirectoryOffset = buf.getInt(); + zipCommentLength = buf.getShort(); + break; + } + } + + int write(OutputStream out) throws IOException { + byte[] buffer = new byte[22]; + ByteBuffer buf = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN); + buf.putInt(signature); + buf.putShort(diskNumber); + buf.putShort(centralDirectoryDiskNumber); + buf.putShort(numEntriesThisDisk); + buf.putShort(numEntries); + buf.putInt(centralDirectorySize); + buf.putInt(centralDirectoryOffset); + buf.putShort((short) 0); // zipCommentLength + out.write(buffer); + return buffer.length; + } + } + + static class CentralHeader { + + static final int MAGIC = 0x02014b50; + + int signature; + short versionMadeBy; + short versionNeededToExtract; + short flags; + short compressionMethod; + short lastModFileTime; + short lastModFileDate; + int crc32; + int compressedSize; + int uncompressedSize; + short fileNameLength; + short extraFieldLength; + short fileCommentLength; + short diskNumberStart; + short internalFileAttributes; + int externalFileAttributes; + int localHeaderOffset; + byte[] filename; + // byte[] extra; + // byte[] comment; + + CentralHeader(RandomAccessFile file) throws IOException { + byte[] buffer = new byte[46]; + file.read(buffer); + ByteBuffer buf = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN); + signature = buf.getInt(); + if (signature != MAGIC) + throw new IOException(); + versionMadeBy = buf.getShort(); + versionNeededToExtract = buf.getShort(); + flags = buf.getShort(); + compressionMethod = buf.getShort(); + lastModFileTime = buf.getShort(); + lastModFileDate = buf.getShort(); + crc32 = buf.getInt(); + compressedSize = buf.getInt(); + uncompressedSize = buf.getInt(); + fileNameLength = buf.getShort(); + extraFieldLength = buf.getShort(); + fileCommentLength = buf.getShort(); + diskNumberStart = buf.getShort(); + internalFileAttributes = buf.getShort(); + externalFileAttributes = buf.getInt(); + localHeaderOffset = buf.getInt(); + filename = new byte[unsigned(fileNameLength)]; + file.read(filename); + file.skipBytes(unsigned(extraFieldLength) + unsigned(fileCommentLength)); + } + + int write(OutputStream out) throws IOException { + byte[] buffer = new byte[46]; + ByteBuffer buf = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN); + buf.putInt(signature); + buf.putShort(versionMadeBy); + buf.putShort(versionNeededToExtract); + buf.putShort(flags); + buf.putShort(compressionMethod); + buf.putShort(lastModFileTime); + buf.putShort(lastModFileDate); + buf.putInt(crc32); + buf.putInt(compressedSize); + buf.putInt(uncompressedSize); + buf.putShort(fileNameLength); + buf.putShort((short) 0); // extraFieldLength + buf.putShort((short) 0); // fileCommentLength + buf.putShort(diskNumberStart); + buf.putShort(internalFileAttributes); + buf.putInt(externalFileAttributes); + buf.putInt(localHeaderOffset); + out.write(buffer); + out.write(filename); + return buffer.length + filename.length; + } + } + + static class LocalHeader { + + static final int MAGIC = 0x04034b50; + + int signature; + short versionNeededToExtract; + short flags; + short compressionMethod; + short lastModFileTime; + short lastModFileDate; + int crc32; + int compressedSize; + int uncompressedSize; + short fileNameLength; + short extraFieldLength; + byte[] filename; + // byte[] extra; + + LocalHeader(RandomAccessFile file) throws IOException { + byte[] buffer = new byte[30]; + file.read(buffer); + ByteBuffer buf = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN); + signature = buf.getInt(); + if (signature != MAGIC) + throw new IOException(); + versionNeededToExtract = buf.getShort(); + flags = buf.getShort(); + compressionMethod = buf.getShort(); + lastModFileTime = buf.getShort(); + lastModFileDate = buf.getShort(); + crc32 = buf.getInt(); + compressedSize = buf.getInt(); + uncompressedSize = buf.getInt(); + fileNameLength = buf.getShort(); + extraFieldLength = buf.getShort(); + file.skipBytes(unsigned(fileNameLength) + unsigned(extraFieldLength)); + } + + int write(OutputStream out) throws IOException { + byte[] buffer = new byte[30]; + ByteBuffer buf = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN); + buf.putInt(signature); + buf.putShort(versionNeededToExtract); + buf.putShort(flags); + buf.putShort(compressionMethod); + buf.putShort(lastModFileTime); + buf.putShort(lastModFileDate); + buf.putInt(crc32); + buf.putInt(compressedSize); + buf.putInt(uncompressedSize); + buf.putShort(fileNameLength); + buf.putShort((short) 0); // extraFieldLength + out.write(buffer); + out.write(filename); + return buffer.length + filename.length; + } + } +} diff --git a/crypto/src/main/java/com/topjohnwu/crypto/ZipSigner.java b/crypto/src/main/java/com/topjohnwu/crypto/ZipSigner.java index c41a5c623..5f3dc0087 100644 --- a/crypto/src/main/java/com/topjohnwu/crypto/ZipSigner.java +++ b/crypto/src/main/java/com/topjohnwu/crypto/ZipSigner.java @@ -15,24 +15,17 @@ import java.security.Security; public class ZipSigner { public static void usage() { - System.err.println("Usage: zipsigner [-m] [x509.pem] [pk8] input.jar output.jar"); - System.err.println("If no certificate/private key pair is specified, it will use the embedded test keys."); - System.err.println(" -m: enable minimal signing"); + System.err.println("Usage: zipsigner [x509.pem] [pk8] input.jar output.jar"); + System.err.println("Note: If no certificate/private key pair is specified, it will use the embedded test keys."); System.exit(2); } public static void main(String[] args) throws Exception { - boolean minSign = false; int argStart = 0; if (args.length < 2) usage(); - if (args[0].equals("-m")) { - minSign = true; - argStart = 1; - } - InputStream cert = null; InputStream key = null; @@ -53,7 +46,7 @@ public class ZipSigner { try (JarMap jar = new JarMap(input, false); BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(output))) { - SignAPK.signZip(cert, key, jar, out, minSign); + SignAPK.signZip(cert, key, jar, out); } } } diff --git a/ziptools/zipadjust.exe b/ziptools/zipadjust.exe deleted file mode 100644 index e15ca943fd02d62907e2f065510b9cf3072662ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 74072 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 diff --git a/ziptools/zipadjust_src/zipadjust.c b/ziptools/zipadjust_src/zipadjust.c deleted file mode 100644 index 0748d1b55..000000000 --- a/ziptools/zipadjust_src/zipadjust.c +++ /dev/null @@ -1,320 +0,0 @@ -/* - * Copyright (C) 2013 Jorrit "Chainfire" Jongma - * Copyright (C) 2013 The OmniROM Project - */ -/* - * This file is part of OpenDelta. - * - * OpenDelta is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * OpenDelta is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with OpenDelta. If not, see . - */ - -#include -#include -#include -#include -#include -#include -#include - -#ifndef O_BINARY -#define O_BINARY 0 -#define O_TEXT 0 -#endif - -#pragma pack(1) -struct local_header_struct { - uint32_t signature; - uint16_t extract_version; - uint16_t flags; - uint16_t compression_method; - uint16_t last_modified_time; - uint16_t last_modified_date; - uint32_t crc32; - uint32_t size_compressed; - uint32_t size_uncompressed; - uint16_t length_filename; - uint16_t length_extra; - // filename - // extra -}; -typedef struct local_header_struct local_header_t; - -#pragma pack(1) -struct data_descriptor_struct { - uint32_t signature; - uint32_t crc32; - uint32_t size_compressed; - uint32_t size_uncompressed; -}; -typedef struct data_descriptor_struct data_descriptor_t; - -#pragma pack(1) -struct central_header_struct { - uint32_t signature; - uint16_t version_made; - uint16_t version_needed; - uint16_t flags; - uint16_t compression_method; - uint16_t last_modified_time; - uint16_t last_modified_date; - uint32_t crc32; - uint32_t size_compressed; - uint32_t size_uncompressed; - uint16_t length_filename; - uint16_t length_extra; - uint16_t length_comment; - uint16_t disk_start; - uint16_t attr_internal; - uint32_t attr_external; - uint32_t offset; - // filename - // extra - // comment -}; -typedef struct central_header_struct central_header_t; - -#pragma pack(1) -struct central_footer_struct { - uint32_t signature; - uint16_t disk_number; - uint16_t disk_number_central_directory; - uint16_t central_directory_entries_this_disk; - uint16_t central_directory_entries_total; - uint32_t central_directory_size; - uint32_t central_directory_offset; - uint16_t length_comment; - // comment -}; -typedef struct central_footer_struct central_footer_t; - -#define MAGIC_LOCAL_HEADER 0x04034b50 -#define MAGIC_DATA_DESCRIPTOR 0x08074b50 -#define MAGIC_CENTRAL_HEADER 0x02014b50 -#define MAGIC_CENTRAL_FOOTER 0x06054b50 - -static int xerror(char* message) { - fprintf(stderr, "%s\n", message); - return 0; -} - -static int xseekread(int fd, off_t offset, void* buf, size_t bytes) { - if (lseek(fd, offset, SEEK_SET) == (off_t)-1) return xerror("Seek failed"); - if (read(fd, buf, bytes) != bytes) return xerror("Read failed"); - return 1; -} - -static int xseekwrite(int fd, off_t offset, void* buf, size_t bytes) { - if (lseek(fd, offset, SEEK_SET) == (off_t)-1) return xerror("Seek failed"); - if (write(fd, buf, bytes) != bytes) return xerror("Write failed"); - return 1; -} - -static int xfilecopy(int fdIn, int fdOut, off_t offsetIn, off_t offsetOut, size_t bytes) { - if ((offsetIn != (off_t)-1) && (lseek(fdIn, offsetIn, SEEK_SET) == (off_t)-1)) return xerror("Seek failed"); - if ((offsetOut != (off_t)-1) && (lseek(fdOut, offsetOut, SEEK_SET) == (off_t)-1)) return xerror("Seek failed"); - - int CHUNK = 256 * 1024; - void* buf = malloc(CHUNK); - if (buf == NULL) return xerror("malloc failed"); - size_t left = bytes; - while (left > 0) { - size_t wanted = (left < CHUNK) ? left : CHUNK; - size_t r = read(fdIn, buf, wanted); - if (r <= 0) return xerror("Read failed"); - if (write(fdOut, buf, r) != r) return xerror("Write failed"); - left -= r; - } - free(buf); - - return 1; -} - -static int xdecompress(int fdIn, int fdOut, off_t offsetIn, off_t offsetOut, size_t bytes) { - if ((offsetIn != (off_t)-1) && (lseek(fdIn, offsetIn, SEEK_SET) == (off_t)-1)) return xerror("Seek failed"); - if ((offsetOut != (off_t)-1) && (lseek(fdOut, offsetOut, SEEK_SET) == (off_t)-1)) return xerror("Seek failed"); - - int CHUNK = 256 * 1024; - - int ret; - unsigned have; - z_stream strm; - unsigned char in[CHUNK]; - unsigned char out[CHUNK]; - - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - strm.avail_in = 0; - strm.next_in = Z_NULL; - ret = inflateInit2(&strm, -15); - if (ret != Z_OK) return xerror("ret != Z_OK"); - - do { - strm.avail_in = read(fdIn, in, CHUNK); - if (strm.avail_in == 0) break; - strm.next_in = in; - - do { - strm.avail_out = CHUNK; - strm.next_out = out; - - ret = inflate(&strm, Z_NO_FLUSH); - if (ret == Z_STREAM_ERROR) return xerror("Stream error"); - switch (ret) { - case Z_NEED_DICT: - ret = Z_DATA_ERROR; - case Z_DATA_ERROR: - case Z_MEM_ERROR: - (void)inflateEnd(&strm); - return xerror("DICT/DATA/MEM error"); - } - - have = CHUNK - strm.avail_out; - if (write(fdOut, out, have) != have) { - (void)inflateEnd(&strm); - return xerror("Write failed"); - } - } while (strm.avail_out == 0); - } while (ret != Z_STREAM_END); - (void)inflateEnd(&strm); - - return ret == Z_STREAM_END ? 1 : 0; -} - -int zipadjust(char* filenameIn, char* filenameOut, int decompress) { - int ok = 0; - - int fin = open(filenameIn, O_RDONLY | O_BINARY); - if (fin > 0) { - unsigned int size = lseek(fin, 0, SEEK_END); - lseek(fin, 0, SEEK_SET); - printf("%d bytes\n", size); - - char filename[1024]; - - central_footer_t central_footer; - uint32_t central_directory_in_position = 0; - uint32_t central_directory_in_size = 0; - uint32_t central_directory_out_size = 0; - - int i; - for (i = size - 4; i >= 0; i--) { - uint32_t magic = 0; - if (!xseekread(fin, i, &magic, sizeof(uint32_t))) return 0; - if (magic == MAGIC_CENTRAL_FOOTER) { - printf("central footer @ %08X\n", i); - if (!xseekread(fin, i, ¢ral_footer, sizeof(central_footer_t))) return 0; - - central_header_t central_header; - if (!xseekread(fin, central_footer.central_directory_offset, ¢ral_header, sizeof(central_header_t))) return 0; - if ( central_header.signature == MAGIC_CENTRAL_HEADER ) { - central_directory_in_position = central_footer.central_directory_offset; - central_directory_in_size = size - central_footer.central_directory_offset; - printf("central header @ %08X (%d)\n", central_footer.central_directory_offset, central_footer.central_directory_size); - break; - } - } - } - - if (central_directory_in_position == 0) return 0; - - unsigned char* central_directory_in = (unsigned char*)malloc(central_directory_in_size); - unsigned char* central_directory_out = (unsigned char*)malloc(central_directory_in_size); - if (!xseekread(fin, central_directory_in_position, central_directory_in, central_directory_in_size)) return 0; - memset(central_directory_out, 0, central_directory_in_size); - - unlink(filenameOut); - int fout = open(filenameOut, O_CREAT | O_WRONLY | O_BINARY, 0644); - if (fout > 0) { - - uintptr_t central_directory_in_index = 0; - uintptr_t central_directory_out_index = 0; - - central_header_t* central_header = NULL; - local_header_t local_header; - - uint32_t out_index = 0; - - while (1) { - central_header = (central_header_t*)¢ral_directory_in[central_directory_in_index]; - if (central_header->signature != MAGIC_CENTRAL_HEADER) break; - - filename[central_header->length_filename] = (char)0; - memcpy(filename, ¢ral_directory_in[central_directory_in_index + sizeof(central_header_t)], central_header->length_filename); - printf("%s (%d --> %d) [%08X] (%d)\n", filename, central_header->size_uncompressed, central_header->size_compressed, central_header->crc32, central_header->length_extra + central_header->length_comment); - - local_header_t local_header; - if (!xseekread(fin, central_header->offset, &local_header, sizeof(local_header_t))) return 0; - - // save and update to next index before we clobber the data - uint16_t compression_method_old = central_header->compression_method; - uint32_t size_compressed_old = central_header->size_compressed; - uint32_t offset_old = central_header->offset; - uint32_t length_extra_old = central_header->length_extra; - central_directory_in_index += sizeof(central_header_t) + central_header->length_filename + central_header->length_extra + central_header->length_comment; - - // copying, rewriting, and correcting local and central headers so all the information matches, and no data descriptors are necessary - central_header->offset = out_index; - central_header->flags = central_header->flags & !8; - if (decompress && (compression_method_old == 8)) { - central_header->compression_method = 0; - central_header->size_compressed = central_header->size_uncompressed; - } - central_header->length_extra = 0; - central_header->length_comment = 0; - local_header.compression_method = central_header->compression_method; - local_header.flags = central_header->flags; - local_header.crc32 = central_header->crc32; - local_header.size_uncompressed = central_header->size_uncompressed; - local_header.size_compressed = central_header->size_compressed; - local_header.length_extra = 0; - - if (!xseekwrite(fout, out_index, &local_header, sizeof(local_header_t))) return 0; - out_index += sizeof(local_header_t); - if (!xseekwrite(fout, out_index, &filename[0], central_header->length_filename)) return 0; - out_index += central_header->length_filename; - - if (decompress && (compression_method_old == 8)) { - if (!xdecompress(fin, fout, offset_old + sizeof(local_header_t) + central_header->length_filename + length_extra_old, out_index, size_compressed_old)) return 0; - } else { - if (!xfilecopy(fin, fout, offset_old + sizeof(local_header_t) + central_header->length_filename + length_extra_old, out_index, size_compressed_old)) return 0; - } - out_index += local_header.size_compressed; - - memcpy(¢ral_directory_out[central_directory_out_index], central_header, sizeof(central_header_t) + central_header->length_filename); - central_directory_out_index += sizeof(central_header_t) + central_header->length_filename; - } - - central_directory_out_size = central_directory_out_index; - central_footer.central_directory_size = central_directory_out_size; - central_footer.central_directory_offset = out_index; - central_footer.length_comment = 0; - if (!xseekwrite(fout, out_index, central_directory_out, central_directory_out_size)) return 0; - out_index += central_directory_out_size; - if (!xseekwrite(fout, out_index, ¢ral_footer, sizeof(central_footer_t))) return 0; - - printf("central header @ %08X (%d)\n", central_footer.central_directory_offset, central_footer.central_directory_size); - printf("central footer @ %08X\n", out_index); - - close(fout); - ok = 1; - } - - free(central_directory_in); - free(central_directory_out); - close(fin); - } - - return ok; -} diff --git a/ziptools/zipadjust_src/zipadjust.h b/ziptools/zipadjust_src/zipadjust.h deleted file mode 100644 index 8ac221633..000000000 --- a/ziptools/zipadjust_src/zipadjust.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2013 Jorrit "Chainfire" Jongma - * Copyright (C) 2013 The OmniROM Project - */ -/* - * This file is part of OpenDelta. - * - * OpenDelta is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * OpenDelta is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with OpenDelta. If not, see . - */ - -#ifndef __ZIPADJUST_H -#define __ZIPADJUST_H - -int zipadjust(char* filenameIn, char* filenameOut, int decompress); - -#endif diff --git a/ziptools/zipadjust_src/zipadjust_run.c b/ziptools/zipadjust_src/zipadjust_run.c deleted file mode 100644 index df7743333..000000000 --- a/ziptools/zipadjust_src/zipadjust_run.c +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2013 Jorrit "Chainfire" Jongma - * Copyright (C) 2013 The OmniROM Project - */ -/* - * This file is part of OpenDelta. - * - * OpenDelta is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * OpenDelta is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with OpenDelta. If not, see . - */ - -#include -#include -#include "zipadjust.h" - -int main(int argc, char *argv[]) { - if (argc >= 3) { - if ((argc >= 4) && (strcmp(argv[1], "--decompress") == 0)) { - zipadjust(argv[2], argv[3], 1); - return 0; - } else { - zipadjust(argv[1], argv[2], 0); - return 0; - } - } - - printf("zipadjust - Copyright (c) 2013 Jorrit Jongma (Chainfire)\n"); - printf("\n"); - printf("Usage: zipadjust [--decompress] input.zip output.zip\n"); - printf("\n"); - printf("Rewrites a zipfile removing all extra fields and comments (this includes the signapk whole-file signature), and synchronizing local headers with the central directory so no data descriptors are needed anymore. Optionally, the output zip is converted to only use STORE.\n"); - printf("\n"); - printf("Written to work specifically with Android OTA zip files, and does not cope with all possible zip file features and formats.\n"); - return 0; -}