import java.io.*; import java.util.*; import java.util.zip.*; /** * Minimal zipalign: copies a zip, padding STORED entries so their data is 4-byte aligned, * and appends any extra files given after the output path (compressed, at the zip root). */ public class ZipAlign { public static void main(String[] a) throws IOException { try (ZipFile in = new ZipFile(a[0]); CountingOut cnt = new CountingOut(new BufferedOutputStream(new FileOutputStream(a[1]))); ZipOutputStream out = new ZipOutputStream(cnt)) { for (ZipEntry e : Collections.list(in.entries())) { ZipEntry n = new ZipEntry(e.getName()); n.setMethod(e.getMethod()); n.setTime(946684800000L); // 2000-01-01, inside the DOS date range (no extra timestamp field) if (e.getMethod() == ZipEntry.STORED) { n.setSize(e.getSize()); n.setCompressedSize(e.getSize()); n.setCrc(e.getCrc()); out.flush(); long dataStart = cnt.count + 30 + n.getName().getBytes("UTF-8").length; int pad = (int) ((4 - dataStart % 4) % 4); if (pad > 0) n.setExtra(padExtra(pad)); } out.putNextEntry(n); in.getInputStream(e).transferTo(out); out.closeEntry(); } for (int i = 2; i < a.length; i++) { File f = new File(a[i]); ZipEntry n = new ZipEntry(f.getName()); n.setTime(946684800000L); out.putNextEntry(n); try (InputStream fin = new FileInputStream(f)) { fin.transferTo(out); } out.closeEntry(); } } } // An extra field needs a 4-byte header, so use one field of pad+4 bytes (Android's 0xD935 alignment tag). static byte[] padExtra(int pad) { byte[] x = new byte[pad + 4]; x[0] = 0x35; x[1] = (byte) 0xD9; x[2] = (byte) pad; return x; } static class CountingOut extends FilterOutputStream { long count; CountingOut(OutputStream o) { super(o); } @Override public void write(int b) throws IOException { out.write(b); count++; } @Override public void write(byte[] b, int off, int len) throws IOException { out.write(b, off, len); count += len; } } }