Port zipadjust to Java

This commit is contained in:
topjohnwu 2018-01-27 08:25:34 +08:00
parent c61135ee7b
commit deae08fc4b
11 changed files with 321 additions and 447 deletions

View File

@ -6,17 +6,15 @@
### Environment Requirements ### Environment Requirements
1. A 64-bit machine: `cmake` for Android is only available in 64-bit 1. Python 3.5+: run `build.py` script
2. Python 3.5+: run `build.py` script 2. Java Development Kit (JDK) 8: Compile Magisk Manager and sign zips
3. 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. 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. 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
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`
### Instructions and Notes ### 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. 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` 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. 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). 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).

2
app

@ -1 +1 @@
Subproject commit f37f330670b1dba2cc76d52d07587c7c605e096a Subproject commit 19af5f9e0be9e725264d85552fd60f46bffff32d

View File

@ -26,13 +26,6 @@ try:
except FileNotFoundError: except FileNotFoundError:
error('Please install Java and make sure \'java\' is available in PATH') 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 argparse
import multiprocessing import multiprocessing
import zipfile import zipfile
@ -326,46 +319,22 @@ def zip_uninstaller(args):
sign_adjust_zip(unsigned, output) sign_adjust_zip(unsigned, output)
def 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) 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): if not os.path.exists(jarsigner):
header('* Building ' + signer_name) header('* Building ' + signer_name)
proc = subprocess.run('{} crypto:shadowJar'.format(os.path.join('.', 'gradlew')), shell=True) proc = subprocess.run('{} crypto:shadowJar'.format(os.path.join('.', 'gradlew')), shell=True)
if proc.returncode != 0: if proc.returncode != 0:
error('Build {} failed!'.format(signer_name)) error('Build {} failed!'.format(signer_name))
header('* Signing / Adjusting Zip') header('* Signing Zip')
signed = tempfile.mkstemp()[1] signed = tempfile.mkstemp()[1]
# Unsigned->signed proc = subprocess.run(['java', '-jar', jarsigner, unsigned, output])
proc = subprocess.run(['java', '-jar', jarsigner, unsigned, signed])
if proc.returncode != 0: if proc.returncode != 0:
error('First sign flashable zip failed!') error('Signing 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)
def cleanup(args): def cleanup(args):
if len(args.target) == 0: if len(args.target) == 0:

View File

@ -15,7 +15,7 @@ jar {
shadowJar { shadowJar {
baseName = 'zipsigner' baseName = 'zipsigner'
classifier = null classifier = null
version = 2.0 version = 2.1
} }
buildscript { buildscript {

View File

@ -18,9 +18,13 @@ import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.bouncycastle.util.encoders.Base64; import org.bouncycastle.util.encoders.Base64;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilterOutputStream; import java.io.FilterOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -67,6 +71,26 @@ public class SignAPK {
Security.insertProviderAt(sBouncyCastleProvider, 1); 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, public static void signZip(InputStream cert, InputStream key,
JarMap input, OutputStream output, boolean minSign) throws Exception { JarMap input, OutputStream output, boolean minSign) throws Exception {
int alignment = 4; int alignment = 4;

View File

@ -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;
}
}
}

View File

@ -15,24 +15,17 @@ import java.security.Security;
public class ZipSigner { public class ZipSigner {
public static void usage() { public static void usage() {
System.err.println("Usage: zipsigner [-m] [x509.pem] [pk8] input.jar output.jar"); System.err.println("Usage: zipsigner [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("Note: If no certificate/private key pair is specified, it will use the embedded test keys.");
System.err.println(" -m: enable minimal signing");
System.exit(2); System.exit(2);
} }
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
boolean minSign = false;
int argStart = 0; int argStart = 0;
if (args.length < 2) if (args.length < 2)
usage(); usage();
if (args[0].equals("-m")) {
minSign = true;
argStart = 1;
}
InputStream cert = null; InputStream cert = null;
InputStream key = null; InputStream key = null;
@ -53,7 +46,7 @@ public class ZipSigner {
try (JarMap jar = new JarMap(input, false); try (JarMap jar = new JarMap(input, false);
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(output))) { BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(output))) {
SignAPK.signZip(cert, key, jar, out, minSign); SignAPK.signZip(cert, key, jar, out);
} }
} }
} }

Binary file not shown.

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <zlib.h>
#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, &central_footer, sizeof(central_footer_t))) return 0;
central_header_t central_header;
if (!xseekread(fin, central_footer.central_directory_offset, &central_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*)&central_directory_in[central_directory_in_index];
if (central_header->signature != MAGIC_CENTRAL_HEADER) break;
filename[central_header->length_filename] = (char)0;
memcpy(filename, &central_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(&central_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, &central_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;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef __ZIPADJUST_H
#define __ZIPADJUST_H
int zipadjust(char* filenameIn, char* filenameOut, int decompress);
#endif

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <string.h>
#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;
}