mirror of
https://github.com/revanced/ARSCLib.git
synced 2025-04-30 06:14:25 +02:00
perf: zip-efficiency
This commit is contained in:
commit
6128b5b75c
@ -268,7 +268,7 @@ public class ApkModule implements ApkFile {
|
|||||||
if(manifest!=null){
|
if(manifest!=null){
|
||||||
manifest.setSort(0);
|
manifest.setSort(0);
|
||||||
}
|
}
|
||||||
ZipSerializer serializer=new ZipSerializer(archive.listInputSources());
|
ZipSerializer serializer=new ZipSerializer(archive.listInputSources(), new ZipAlign());
|
||||||
serializer.setWriteProgress(progress);
|
serializer.setWriteProgress(progress);
|
||||||
serializer.setWriteInterceptor(interceptor);
|
serializer.setWriteInterceptor(interceptor);
|
||||||
serializer.writeZip(file);
|
serializer.writeZip(file);
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
package com.reandroid.archive;
|
package com.reandroid.archive;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -35,17 +36,14 @@ public class APKArchive extends ZipArchive {
|
|||||||
sortApkFiles(new ArrayList<>(listInputSources()));
|
sortApkFiles(new ArrayList<>(listInputSources()));
|
||||||
}
|
}
|
||||||
public long writeApk(File outApk) throws IOException{
|
public long writeApk(File outApk) throws IOException{
|
||||||
ZipSerializer serializer=new ZipSerializer(listInputSources());
|
ZipSerializer serializer=new ZipSerializer(listInputSources(), new ZipAlign());
|
||||||
return serializer.writeZip(outApk);
|
return serializer.writeZip(outApk);
|
||||||
}
|
}
|
||||||
public long writeApk(OutputStream outputStream) throws IOException{
|
public long writeApk(OutputStream outputStream) throws IOException{
|
||||||
ZipSerializer serializer=new ZipSerializer(listInputSources());
|
ZipSerializer serializer=new ZipSerializer(listInputSources(), new ZipAlign());
|
||||||
return serializer.writeZip(outputStream);
|
return serializer.writeZip(outputStream);
|
||||||
}
|
}
|
||||||
public static APKArchive loadZippedApk(File zipFile) throws IOException {
|
public static APKArchive loadZippedApk(File zipFile) throws IOException {
|
||||||
return loadZippedApk(new ZipFile(zipFile));
|
|
||||||
}
|
|
||||||
public static APKArchive loadZippedApk(ZipFile zipFile) {
|
|
||||||
Map<String, InputSource> entriesMap = InputSourceUtil.mapZipFileSources(zipFile);
|
Map<String, InputSource> entriesMap = InputSourceUtil.mapZipFileSources(zipFile);
|
||||||
return new APKArchive(entriesMap);
|
return new APKArchive(entriesMap);
|
||||||
}
|
}
|
||||||
|
@ -18,8 +18,7 @@ package com.reandroid.archive;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.util.zip.CRC32;
|
import java.util.zip.*;
|
||||||
import java.util.zip.ZipEntry;
|
|
||||||
|
|
||||||
public abstract class InputSource {
|
public abstract class InputSource {
|
||||||
private final String name;
|
private final String name;
|
||||||
@ -28,67 +27,119 @@ public abstract class InputSource {
|
|||||||
private long mLength;
|
private long mLength;
|
||||||
private int method = ZipEntry.DEFLATED;
|
private int method = ZipEntry.DEFLATED;
|
||||||
private int sort;
|
private int sort;
|
||||||
public InputSource(String name){
|
|
||||||
|
public InputSource(String name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.alias = InputSourceUtil.sanitize(name);
|
this.alias = InputSourceUtil.sanitize(name);
|
||||||
}
|
}
|
||||||
public void disposeInputSource(){
|
|
||||||
|
public void disposeInputSource() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getSort() {
|
public int getSort() {
|
||||||
return sort;
|
return sort;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSort(int sort) {
|
public void setSort(int sort) {
|
||||||
this.sort = sort;
|
this.sort = sort;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getMethod() {
|
public int getMethod() {
|
||||||
return method;
|
return method;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMethod(int method) {
|
public void setMethod(int method) {
|
||||||
this.method = method;
|
this.method = method;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAlias(){
|
public String getAlias() {
|
||||||
if(alias!=null){
|
if (alias != null) {
|
||||||
return alias;
|
return alias;
|
||||||
}
|
}
|
||||||
return getName();
|
return getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAlias(String alias) {
|
public void setAlias(String alias) {
|
||||||
this.alias = alias;
|
this.alias = alias;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void close(InputStream inputStream) throws IOException {
|
public void close(InputStream inputStream) throws IOException {
|
||||||
inputStream.close();
|
inputStream.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
public long write(OutputStream outputStream) throws IOException {
|
public long write(OutputStream outputStream) throws IOException {
|
||||||
return write(outputStream, openStream());
|
return write(outputStream, openStream());
|
||||||
}
|
}
|
||||||
private long write(OutputStream outputStream, InputStream inputStream) throws IOException {
|
|
||||||
long result=0;
|
protected final long write(OutputStream outputStream, InputStream inputStream) throws IOException {
|
||||||
byte[] buffer=new byte[10240];
|
long result = 0;
|
||||||
|
byte[] buffer = new byte[10240];
|
||||||
int len;
|
int len;
|
||||||
while ((len=inputStream.read(buffer))>0){
|
while ((len = inputStream.read(buffer)) > 0) {
|
||||||
outputStream.write(buffer, 0, len);
|
outputStream.write(buffer, 0, len);
|
||||||
result+=len;
|
result += len;
|
||||||
}
|
}
|
||||||
close(inputStream);
|
close(inputStream);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
public String getName(){
|
|
||||||
|
public static class WriteCompressedResult {
|
||||||
|
public long crc;
|
||||||
|
public long compressedSize;
|
||||||
|
public long uncompressedSize;
|
||||||
|
|
||||||
|
WriteCompressedResult(long crc, long compressedSize, long uncompressedSize) {
|
||||||
|
this.crc = crc;
|
||||||
|
this.compressedSize = compressedSize;
|
||||||
|
this.uncompressedSize = uncompressedSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public WriteCompressedResult writeCompressed(OutputStream outputStream) throws IOException {
|
||||||
|
CRC32 checksum = null;
|
||||||
|
Deflater deflater = new Deflater(Deflater.DEFAULT_COMPRESSION, true);
|
||||||
|
DeflaterOutputStream deflatedStream = new DeflaterOutputStream(outputStream, deflater);
|
||||||
|
|
||||||
|
OutputStream targetStream;
|
||||||
|
if (mCrc == 0) {
|
||||||
|
checksum = new CRC32();
|
||||||
|
targetStream = new CheckedOutputStream(deflatedStream, checksum);
|
||||||
|
} else {
|
||||||
|
targetStream = deflatedStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
write(targetStream);
|
||||||
|
targetStream.flush();
|
||||||
|
deflatedStream.finish();
|
||||||
|
|
||||||
|
mLength = deflater.getBytesRead();
|
||||||
|
if (checksum != null) {
|
||||||
|
mCrc = checksum.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new WriteCompressedResult(mCrc, deflater.getBytesWritten(), mLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
public long getLength() throws IOException{
|
|
||||||
if(mLength==0){
|
public long getLength() throws IOException {
|
||||||
|
if (mLength == 0) {
|
||||||
calculateCrc();
|
calculateCrc();
|
||||||
}
|
}
|
||||||
return mLength;
|
return mLength;
|
||||||
}
|
}
|
||||||
public long getCrc() throws IOException{
|
|
||||||
if(mCrc==0){
|
public long getCrc() throws IOException {
|
||||||
|
if (mCrc == 0) {
|
||||||
calculateCrc();
|
calculateCrc();
|
||||||
}
|
}
|
||||||
return mCrc;
|
return mCrc;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract InputStream openStream() throws IOException;
|
public abstract InputStream openStream() throws IOException;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) {
|
if (this == o) {
|
||||||
@ -100,26 +151,29 @@ public abstract class InputSource {
|
|||||||
InputSource that = (InputSource) o;
|
InputSource that = (InputSource) o;
|
||||||
return getName().equals(that.getName());
|
return getName().equals(that.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return getName().hashCode();
|
return getName().hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString(){
|
public String toString() {
|
||||||
return getClass().getSimpleName()+": "+getName();
|
return getClass().getSimpleName() + ": " + getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void calculateCrc() throws IOException {
|
private void calculateCrc() throws IOException {
|
||||||
InputStream inputStream=openStream();
|
InputStream inputStream = openStream();
|
||||||
long length=0;
|
long length = 0;
|
||||||
CRC32 crc = new CRC32();
|
CRC32 crc = new CRC32();
|
||||||
int bytesRead;
|
int bytesRead;
|
||||||
byte[] buffer = new byte[1024*64];
|
byte[] buffer = new byte[1024 * 64];
|
||||||
while((bytesRead = inputStream.read(buffer)) != -1) {
|
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||||
crc.update(buffer, 0, bytesRead);
|
crc.update(buffer, 0, bytesRead);
|
||||||
length+=bytesRead;
|
length += bytesRead;
|
||||||
}
|
}
|
||||||
close(inputStream);
|
close(inputStream);
|
||||||
mCrc=crc.getValue();
|
mCrc = crc.getValue();
|
||||||
mLength=length;
|
mLength = length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,23 +41,16 @@ import java.util.zip.ZipInputStream;
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Map<String, InputSource> mapZipFileSources(ZipFile zipFile){
|
public static Map<String, InputSource> mapZipFileSources(File zipFile) throws IOException {
|
||||||
Map<String, InputSource> results=new LinkedHashMap<>();
|
ZipDeserializer deserializer = new ZipDeserializer(zipFile);
|
||||||
Enumeration<? extends ZipEntry> entriesEnum = zipFile.entries();
|
return deserializer.mapInputSources();
|
||||||
int i=0;
|
|
||||||
while (entriesEnum.hasMoreElements()){
|
|
||||||
ZipEntry zipEntry = entriesEnum.nextElement();
|
|
||||||
if(zipEntry.isDirectory()){
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
ZipEntrySource source=new ZipEntrySource(zipFile, zipEntry);
|
|
||||||
source.setSort(i);
|
|
||||||
source.setMethod(zipEntry.getMethod());
|
|
||||||
results.put(source.getName(), source);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static List<InputSource> listZipFileSources(File zipFile) throws IOException {
|
||||||
|
ZipDeserializer deserializer = new ZipDeserializer(zipFile);
|
||||||
|
return deserializer.listInputSources();
|
||||||
|
}
|
||||||
|
|
||||||
public static Map<String, ByteInputSource> mapInputStreamAsBuffer(InputStream inputStream) throws IOException {
|
public static Map<String, ByteInputSource> mapInputStreamAsBuffer(InputStream inputStream) throws IOException {
|
||||||
Map<String, ByteInputSource> results = new LinkedHashMap<>();
|
Map<String, ByteInputSource> results = new LinkedHashMap<>();
|
||||||
ZipInputStream zin = new ZipInputStream(inputStream);
|
ZipInputStream zin = new ZipInputStream(inputStream);
|
||||||
@ -88,21 +81,7 @@ import java.util.zip.ZipInputStream;
|
|||||||
outputStream.close();
|
outputStream.close();
|
||||||
return outputStream.toByteArray();
|
return outputStream.toByteArray();
|
||||||
}
|
}
|
||||||
public static List<InputSource> listZipFileSources(ZipFile zipFile){
|
|
||||||
List<InputSource> results=new ArrayList<>();
|
|
||||||
Enumeration<? extends ZipEntry> entriesEnum = zipFile.entries();
|
|
||||||
int i=0;
|
|
||||||
while (entriesEnum.hasMoreElements()){
|
|
||||||
ZipEntry zipEntry = entriesEnum.nextElement();
|
|
||||||
if(zipEntry.isDirectory()){
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
ZipEntrySource source=new ZipEntrySource(zipFile, zipEntry);
|
|
||||||
source.setSort(i);
|
|
||||||
results.add(source);
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
public static List<InputSource> listDirectory(File dir){
|
public static List<InputSource> listDirectory(File dir){
|
||||||
List<InputSource> results=new ArrayList<>();
|
List<InputSource> results=new ArrayList<>();
|
||||||
recursiveDirectory(results, dir, dir);
|
recursiveDirectory(results, dir, dir);
|
||||||
|
@ -5,299 +5,58 @@
|
|||||||
|
|
||||||
package com.reandroid.archive;
|
package com.reandroid.archive;
|
||||||
|
|
||||||
import java.io.BufferedOutputStream;
|
import java.io.*;
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.FilterOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.RandomAccessFile;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Calendar;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.GregorianCalendar;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipFile;
|
|
||||||
import java.util.zip.ZipOutputStream;
|
|
||||||
|
|
||||||
|
|
||||||
public class ZipAlign {
|
public class ZipAlign {
|
||||||
private static final int ZIP_ENTRY_HEADER_LEN = 30;
|
|
||||||
private static final int ZIP_ENTRY_VERSION = 20;
|
|
||||||
private static final int ZIP_ENTRY_USES_DATA_DESCR = 0x0008;
|
|
||||||
private static final int ZIP_ENTRY_DATA_DESCRIPTOR_LEN = 16;
|
|
||||||
private static final int ALIGNMENT_4 = 4;
|
private static final int ALIGNMENT_4 = 4;
|
||||||
private static final int ALIGNMENT_PAGE = 4096;
|
private static final int ALIGNMENT_PAGE = 4096;
|
||||||
|
private final int mAlignment;
|
||||||
|
|
||||||
private static class XEntry {
|
public ZipAlign(int mAlignment) {
|
||||||
public final ZipEntry entry;
|
this.mAlignment = mAlignment;
|
||||||
public final long headerOffset;
|
|
||||||
public final int flags;
|
|
||||||
public final int padding;
|
|
||||||
|
|
||||||
public XEntry(ZipEntry entry, long headerOffset, int flags, int padding) {
|
|
||||||
this.entry = entry;
|
|
||||||
this.headerOffset = headerOffset;
|
|
||||||
this.flags = flags;
|
|
||||||
this.padding = padding;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
public ZipAlign() {
|
||||||
|
this(ALIGNMENT_4);
|
||||||
private static class FilterOutputStreamEx extends FilterOutputStream {
|
|
||||||
private long totalWritten = 0;
|
|
||||||
public FilterOutputStreamEx(OutputStream out) {
|
|
||||||
super(out);
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void write(byte[] b) throws IOException {
|
|
||||||
out.write(b);
|
|
||||||
totalWritten += b.length;
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void write(byte[] b, int off, int len) throws IOException {
|
|
||||||
out.write(b, off, len);
|
|
||||||
totalWritten += len;
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void write(int b) throws IOException {
|
|
||||||
out.write(b);
|
|
||||||
totalWritten += 1;
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException {
|
|
||||||
super.close();
|
|
||||||
}
|
|
||||||
public void writeInt(long v) throws IOException {
|
|
||||||
write((int) (v & 0xff));
|
|
||||||
write((int) ((v >>> 8) & 0xff));
|
|
||||||
write((int) ((v >>> 16) & 0xff));
|
|
||||||
write((int) ((v >>> 24) & 0xff));
|
|
||||||
}
|
|
||||||
public void writeShort(int v) throws IOException {
|
|
||||||
write((v) & 0xff);
|
|
||||||
write((v >>> 8) & 0xff);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private File mInputFile;
|
|
||||||
private int mAlignment;
|
|
||||||
private File mOutputFile;
|
|
||||||
private ZipFile mZipFile;
|
|
||||||
private RandomAccessFile mRafInput;
|
|
||||||
private FilterOutputStreamEx mOutputStream;
|
|
||||||
private final List<XEntry> mXEntries = new ArrayList<>();
|
|
||||||
private long mInputFileOffset = 0;
|
|
||||||
private int mTotalPadding = 0;
|
|
||||||
|
|
||||||
public void zipAlign(File input, File output) throws IOException {
|
public void zipAlign(File input, File output) throws IOException {
|
||||||
zipAlign(input, output, ALIGNMENT_4);
|
ZipDeserializer zipDeserializer = new ZipDeserializer(input);
|
||||||
}
|
|
||||||
public void zipAlign(File input, File output, int alignment) throws IOException {
|
|
||||||
mInputFile = input;
|
|
||||||
mAlignment = alignment;
|
|
||||||
mOutputFile = output;
|
|
||||||
openFiles();
|
|
||||||
copyAllEntries();
|
|
||||||
buildCentralDirectory();
|
|
||||||
closeFiles();
|
|
||||||
}
|
|
||||||
private void openFiles() throws IOException {
|
|
||||||
mZipFile = new ZipFile(mInputFile);
|
|
||||||
mRafInput = new RandomAccessFile(mInputFile, "r");
|
|
||||||
mOutputStream = new FilterOutputStreamEx(new BufferedOutputStream(new FileOutputStream(mOutputFile), 32 * 1024));
|
|
||||||
}
|
|
||||||
private void copyAllEntries() throws IOException {
|
|
||||||
final int entryCount = mZipFile.size();
|
|
||||||
if (entryCount == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final Enumeration<?> entries = mZipFile.entries();
|
|
||||||
while (entries.hasMoreElements()) {
|
|
||||||
final ZipEntry entry = (ZipEntry) entries.nextElement();
|
|
||||||
final String name = entry.getName();
|
|
||||||
|
|
||||||
int flags = entry.getMethod() == ZipEntry.STORED ? 0 : 1 << 3;
|
|
||||||
flags |= 1 << 11;
|
|
||||||
|
|
||||||
final long outputEntryHeaderOffset = mOutputStream.totalWritten;
|
|
||||||
|
|
||||||
final int inputEntryHeaderSize = ZIP_ENTRY_HEADER_LEN + (entry.getExtra() != null ? entry.getExtra().length : 0)
|
|
||||||
+ name.getBytes(StandardCharsets.UTF_8).length;
|
|
||||||
final long inputEntryDataOffset = mInputFileOffset + inputEntryHeaderSize;
|
|
||||||
|
|
||||||
final int padding;
|
|
||||||
|
|
||||||
if (entry.getMethod() != ZipEntry.STORED) {
|
|
||||||
padding = 0;
|
|
||||||
} else {
|
|
||||||
int alignment = mAlignment;
|
|
||||||
if (name.startsWith("lib/") && name.endsWith(".so")) {
|
|
||||||
alignment = ALIGNMENT_PAGE;
|
|
||||||
}
|
|
||||||
long newOffset = inputEntryDataOffset + mTotalPadding;
|
|
||||||
padding = (int) ((alignment - (newOffset % alignment)) % alignment);
|
|
||||||
mTotalPadding += padding;
|
|
||||||
}
|
|
||||||
|
|
||||||
final XEntry xentry = new XEntry(entry, outputEntryHeaderOffset, flags, padding);
|
|
||||||
mXEntries.add(xentry);
|
|
||||||
byte[] extra = entry.getExtra();
|
|
||||||
if (extra == null) {
|
|
||||||
extra = new byte[padding];
|
|
||||||
} else {
|
|
||||||
byte[] newExtra = new byte[extra.length + padding];
|
|
||||||
System.arraycopy(extra, 0, newExtra, 0, extra.length);
|
|
||||||
Arrays.fill(newExtra, extra.length, newExtra.length, (byte) 0);
|
|
||||||
extra = newExtra;
|
|
||||||
}
|
|
||||||
entry.setExtra(extra);
|
|
||||||
mOutputStream.writeInt(ZipOutputStream.LOCSIG);
|
|
||||||
mOutputStream.writeShort(ZIP_ENTRY_VERSION);
|
|
||||||
mOutputStream.writeShort(flags);
|
|
||||||
mOutputStream.writeShort(entry.getMethod());
|
|
||||||
|
|
||||||
int modDate;
|
|
||||||
int time;
|
|
||||||
GregorianCalendar cal = new GregorianCalendar();
|
|
||||||
cal.setTime(new Date(entry.getTime()));
|
|
||||||
int year = cal.get(Calendar.YEAR);
|
|
||||||
if (year < 1980) {
|
|
||||||
modDate = 0x21;
|
|
||||||
time = 0;
|
|
||||||
} else {
|
|
||||||
modDate = cal.get(Calendar.DATE);
|
|
||||||
modDate = (cal.get(Calendar.MONTH) + 1 << 5) | modDate;
|
|
||||||
modDate = ((cal.get(Calendar.YEAR) - 1980) << 9) | modDate;
|
|
||||||
time = cal.get(Calendar.SECOND) >> 1;
|
|
||||||
time = (cal.get(Calendar.MINUTE) << 5) | time;
|
|
||||||
time = (cal.get(Calendar.HOUR_OF_DAY) << 11) | time;
|
|
||||||
}
|
|
||||||
|
|
||||||
mOutputStream.writeShort(time);
|
|
||||||
mOutputStream.writeShort(modDate);
|
|
||||||
|
|
||||||
mOutputStream.writeInt(entry.getCrc());
|
|
||||||
mOutputStream.writeInt(entry.getCompressedSize());
|
|
||||||
mOutputStream.writeInt(entry.getSize());
|
|
||||||
|
|
||||||
mOutputStream.writeShort(entry.getName().getBytes(StandardCharsets.UTF_8).length);
|
|
||||||
mOutputStream.writeShort(entry.getExtra().length);
|
|
||||||
mOutputStream.write(entry.getName().getBytes(StandardCharsets.UTF_8));
|
|
||||||
mOutputStream.write(entry.getExtra(), 0, entry.getExtra().length);
|
|
||||||
|
|
||||||
mInputFileOffset += inputEntryHeaderSize;
|
|
||||||
|
|
||||||
final long sizeToCopy;
|
|
||||||
if ((flags & ZIP_ENTRY_USES_DATA_DESCR) != 0) {
|
|
||||||
sizeToCopy = (entry.isDirectory() ? 0 : entry.getCompressedSize()) + ZIP_ENTRY_DATA_DESCRIPTOR_LEN;
|
|
||||||
} else {
|
|
||||||
sizeToCopy = entry.isDirectory() ? 0 : entry.getCompressedSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sizeToCopy > 0) {
|
|
||||||
mRafInput.seek(mInputFileOffset);
|
|
||||||
|
|
||||||
long totalSizeCopied = 0;
|
|
||||||
final byte[] buf = new byte[32 * 1024];
|
|
||||||
while (totalSizeCopied < sizeToCopy) {
|
|
||||||
int read = mRafInput.read(buf, 0, (int) Math.min(32 * 1024, sizeToCopy - totalSizeCopied));
|
|
||||||
if (read <= 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
mOutputStream.write(buf, 0, read);
|
|
||||||
totalSizeCopied += read;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mInputFileOffset += sizeToCopy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void buildCentralDirectory() throws IOException {
|
|
||||||
final long centralDirOffset = mOutputStream.totalWritten;
|
|
||||||
final int entryCount = mXEntries.size();
|
|
||||||
for (int i = 0; i < entryCount; i++) {
|
|
||||||
XEntry xentry = mXEntries.get(i);
|
|
||||||
final ZipEntry entry = xentry.entry;
|
|
||||||
int modDate;
|
|
||||||
int time;
|
|
||||||
GregorianCalendar cal = new GregorianCalendar();
|
|
||||||
cal.setTime(new Date(entry.getTime()));
|
|
||||||
int year = cal.get(Calendar.YEAR);
|
|
||||||
if (year < 1980) {
|
|
||||||
modDate = 0x21;
|
|
||||||
time = 0;
|
|
||||||
} else {
|
|
||||||
modDate = cal.get(Calendar.DATE);
|
|
||||||
modDate = (cal.get(Calendar.MONTH) + 1 << 5) | modDate;
|
|
||||||
modDate = ((cal.get(Calendar.YEAR) - 1980) << 9) | modDate;
|
|
||||||
time = cal.get(Calendar.SECOND) >> 1;
|
|
||||||
time = (cal.get(Calendar.MINUTE) << 5) | time;
|
|
||||||
time = (cal.get(Calendar.HOUR_OF_DAY) << 11) | time;
|
|
||||||
}
|
|
||||||
|
|
||||||
mOutputStream.writeInt(ZipFile.CENSIG); // CEN header signature
|
|
||||||
mOutputStream.writeShort(ZIP_ENTRY_VERSION); // version made by
|
|
||||||
mOutputStream.writeShort(ZIP_ENTRY_VERSION); // version needed to extract
|
|
||||||
mOutputStream.writeShort(xentry.flags); // general purpose bit flag
|
|
||||||
mOutputStream.writeShort(entry.getMethod()); // compression method
|
|
||||||
mOutputStream.writeShort(time);
|
|
||||||
mOutputStream.writeShort(modDate);
|
|
||||||
mOutputStream.writeInt(entry.getCrc()); // crc-32
|
|
||||||
mOutputStream.writeInt(entry.getCompressedSize()); // compressed size
|
|
||||||
mOutputStream.writeInt(entry.getSize()); // uncompressed size
|
|
||||||
final byte[] nameBytes = entry.getName().getBytes(StandardCharsets.UTF_8);
|
|
||||||
mOutputStream.writeShort(nameBytes.length);
|
|
||||||
mOutputStream.writeShort(entry.getExtra() != null ? entry.getExtra().length - xentry.padding : 0);
|
|
||||||
final byte[] commentBytes;
|
|
||||||
if (entry.getComment() != null) {
|
|
||||||
commentBytes = entry.getComment().getBytes(StandardCharsets.UTF_8);
|
|
||||||
mOutputStream.writeShort(Math.min(commentBytes.length, 0xffff));
|
|
||||||
} else {
|
|
||||||
commentBytes = null;
|
|
||||||
mOutputStream.writeShort(0);
|
|
||||||
}
|
|
||||||
mOutputStream.writeShort(0); // starting disk number
|
|
||||||
mOutputStream.writeShort(0); // internal file attributes (unused)
|
|
||||||
mOutputStream.writeInt(0); // external file attributes (unused)
|
|
||||||
mOutputStream.writeInt(xentry.headerOffset); // relative offset of local header
|
|
||||||
mOutputStream.write(nameBytes);
|
|
||||||
if (entry.getExtra() != null) {
|
|
||||||
mOutputStream.write(entry.getExtra(), 0, entry.getExtra().length - xentry.padding);
|
|
||||||
}
|
|
||||||
if (commentBytes != null) {
|
|
||||||
mOutputStream.write(commentBytes, 0, Math.min(commentBytes.length, 0xffff));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
final long centralDirSize = mOutputStream.totalWritten - centralDirOffset;
|
|
||||||
|
|
||||||
mOutputStream.writeInt(ZipFile.ENDSIG); // END record signature
|
|
||||||
mOutputStream.writeShort(0); // number of this disk
|
|
||||||
mOutputStream.writeShort(0); // central directory start disk
|
|
||||||
mOutputStream.writeShort(entryCount); // number of directory entries on disk
|
|
||||||
mOutputStream.writeShort(entryCount); // total number of directory entries
|
|
||||||
mOutputStream.writeInt(centralDirSize); // length of central directory
|
|
||||||
mOutputStream.writeInt(centralDirOffset); // offset of central directory
|
|
||||||
mOutputStream.writeShort(0);
|
|
||||||
mOutputStream.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void closeFiles() throws IOException {
|
|
||||||
try {
|
try {
|
||||||
mZipFile.close();
|
ZipSerializer zipSerializer = new ZipSerializer(zipDeserializer.listInputSources(), this);
|
||||||
|
zipSerializer.writeZip(output);
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
zipDeserializer.closeChannel();
|
||||||
mRafInput.close();
|
}
|
||||||
} finally {
|
}
|
||||||
mOutputStream.close();
|
|
||||||
|
public int alignEntry(ZipEntry entry, long dataOffset) {
|
||||||
|
String name = entry.getName();
|
||||||
|
final int padding;
|
||||||
|
|
||||||
|
if (entry.getMethod() != ZipEntry.STORED) {
|
||||||
|
padding = 0;
|
||||||
|
} else {
|
||||||
|
int alignment = mAlignment;
|
||||||
|
if (name.startsWith("lib/") && name.endsWith(".so")) {
|
||||||
|
alignment = ALIGNMENT_PAGE;
|
||||||
}
|
}
|
||||||
|
padding = (int) ((alignment - (dataOffset % alignment)) % alignment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
byte[] extra = entry.getExtra();
|
||||||
|
if (extra == null) {
|
||||||
|
extra = new byte[padding];
|
||||||
|
} else {
|
||||||
|
byte[] newExtra = new byte[extra.length + padding];
|
||||||
|
System.arraycopy(extra, 0, newExtra, 0, extra.length);
|
||||||
|
Arrays.fill(newExtra, extra.length, newExtra.length, (byte) 0);
|
||||||
|
extra = newExtra;
|
||||||
|
}
|
||||||
|
entry.setExtra(extra);
|
||||||
|
|
||||||
|
return padding;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void align4(File inFile) throws IOException{
|
public static void align4(File inFile) throws IOException{
|
||||||
@ -314,12 +73,12 @@ public class ZipAlign {
|
|||||||
tmp.renameTo(inFile);
|
tmp.renameTo(inFile);
|
||||||
}
|
}
|
||||||
public static void align(File inFile, File outFile, int alignment) throws IOException{
|
public static void align(File inFile, File outFile, int alignment) throws IOException{
|
||||||
ZipAlign zipAlign=new ZipAlign();
|
ZipAlign zipAlign=new ZipAlign(alignment);
|
||||||
File dir=outFile.getParentFile();
|
File dir=outFile.getParentFile();
|
||||||
if(dir!=null && !dir.exists()){
|
if(dir!=null && !dir.exists()){
|
||||||
dir.mkdirs();
|
dir.mkdirs();
|
||||||
}
|
}
|
||||||
zipAlign.zipAlign(inFile, outFile, alignment);
|
zipAlign.zipAlign(inFile, outFile);
|
||||||
}
|
}
|
||||||
private static File toTmpFile(File file){
|
private static File toTmpFile(File file){
|
||||||
String name=file.getName()+".align.tmp";
|
String name=file.getName()+".align.tmp";
|
||||||
|
@ -85,16 +85,12 @@ public class ZipArchive {
|
|||||||
return inputSource;
|
return inputSource;
|
||||||
}
|
}
|
||||||
public void addArchive(File archiveFile) throws IOException {
|
public void addArchive(File archiveFile) throws IOException {
|
||||||
ZipFile zipFile=new ZipFile(archiveFile);
|
List<InputSource> sourceList = InputSourceUtil.listZipFileSources(archiveFile);
|
||||||
add(zipFile);
|
this.addAll(sourceList);
|
||||||
}
|
}
|
||||||
public void addDirectory(File dir){
|
public void addDirectory(File dir){
|
||||||
addAll(InputSourceUtil.listDirectory(dir));
|
addAll(InputSourceUtil.listDirectory(dir));
|
||||||
}
|
}
|
||||||
public void add(ZipFile zipFile){
|
|
||||||
List<InputSource> sourceList = InputSourceUtil.listZipFileSources(zipFile);
|
|
||||||
this.addAll(sourceList);
|
|
||||||
}
|
|
||||||
public void addAll(Collection<? extends InputSource> inputSourceList){
|
public void addAll(Collection<? extends InputSource> inputSourceList){
|
||||||
for(InputSource inputSource:inputSourceList){
|
for(InputSource inputSource:inputSourceList){
|
||||||
add(inputSource);
|
add(inputSource);
|
||||||
|
153
src/main/java/com/reandroid/archive/ZipDeserializer.java
Normal file
153
src/main/java/com/reandroid/archive/ZipDeserializer.java
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
package com.reandroid.archive;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.channels.Channels;
|
||||||
|
import java.nio.channels.FileChannel;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
|
||||||
|
public class ZipDeserializer {
|
||||||
|
private final FileChannel channel;
|
||||||
|
private FilterInputStreamEx inputStream;
|
||||||
|
|
||||||
|
public ZipDeserializer(File file) throws IOException {
|
||||||
|
this.channel = FileChannel.open(file.toPath(), StandardOpenOption.READ);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long dataOffset(long headerOffset, ZipEntry entry) {
|
||||||
|
final byte[] extra = entry.getExtra();
|
||||||
|
return headerOffset + ZipEntry.LOCHDR + (extra != null ? extra.length : 0) + entry.getName().getBytes(StandardCharsets.UTF_8).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void closeChannel() throws IOException {
|
||||||
|
channel.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<InputSource> listInputSources() throws IOException {
|
||||||
|
inputStream = new FilterInputStreamEx(Channels.newInputStream(channel));
|
||||||
|
ArrayList<InputSource> list = new ArrayList<>();
|
||||||
|
channel.position(channel.size() - ZipEntry.ENDHDR);
|
||||||
|
|
||||||
|
int commentSize = 0;
|
||||||
|
while (inputStream.readInt() != ZipEntry.ENDSIG) {
|
||||||
|
channel.position(channel.position() - 5);
|
||||||
|
if (commentSize > 65535) {
|
||||||
|
throw new IOException("Could not find central directory");
|
||||||
|
}
|
||||||
|
commentSize++;
|
||||||
|
}
|
||||||
|
|
||||||
|
skip(ZipEntry.ENDTOT - 4);
|
||||||
|
int numRecords = inputStream.readShort(); // number of entries
|
||||||
|
skip(4); // size of central directory
|
||||||
|
long centralDirectoryOffset = inputStream.readInt(); // central directory offset.
|
||||||
|
|
||||||
|
channel.position(centralDirectoryOffset);
|
||||||
|
int sort = 0;
|
||||||
|
for (int i = 0; i < numRecords; i++) {
|
||||||
|
ZipEntrySource inputSource = readCentralDirRecord();
|
||||||
|
if (inputSource != null) {
|
||||||
|
inputSource.setSort(sort);
|
||||||
|
list.add(inputSource);
|
||||||
|
sort += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, InputSource> mapInputSources() throws IOException {
|
||||||
|
final LinkedHashMap<String, InputSource> map = new LinkedHashMap<>();
|
||||||
|
for (InputSource inputSource : listInputSources()) {
|
||||||
|
map.put(inputSource.getName(), inputSource);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateSignature(long actual, long expected, String recordType) throws IOException {
|
||||||
|
if (actual != expected) {
|
||||||
|
throw new IOException("Invalid " + recordType + " signature.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ZipEntrySource readCentralDirRecord() throws IOException {
|
||||||
|
validateSignature(inputStream.readInt(), ZipEntry.CENSIG, "central directory");
|
||||||
|
skip(6); // versions and general purpose bit flag.
|
||||||
|
int method = inputStream.readShort(); // compression method
|
||||||
|
skip(4); // modification date/time
|
||||||
|
long crc = inputStream.readInt(); // crc-32
|
||||||
|
long compressedSize = inputStream.readInt(); // compressed size
|
||||||
|
long uncompressedSize = inputStream.readInt(); // uncompressed size
|
||||||
|
int nameLength = inputStream.readShort(); // name length
|
||||||
|
int extraLength = inputStream.readShort(); // extra field length (central directory)
|
||||||
|
int commentLength = inputStream.readShort(); // comment length
|
||||||
|
skip(8); // attributes and disk number.
|
||||||
|
long headerOffset = inputStream.readInt(); // location of the entry
|
||||||
|
String name = new String(inputStream.readBytes(nameLength)); // name
|
||||||
|
byte[] extra = readLocalExtraField(headerOffset, nameLength); // extra (from local entry)
|
||||||
|
skip(extraLength); // skip the extra field of the central directory.
|
||||||
|
String comment = new String(inputStream.readBytes(commentLength)); // comment
|
||||||
|
|
||||||
|
ZipEntry entry = new ZipEntry(name);
|
||||||
|
entry.setExtra(extra);
|
||||||
|
entry.setComment(comment);
|
||||||
|
entry.setMethod(method);
|
||||||
|
entry.setCompressedSize(compressedSize);
|
||||||
|
entry.setSize(uncompressedSize);
|
||||||
|
entry.setCrc(crc);
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ZipEntrySource(entry, headerOffset, channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] readLocalExtraField(long headerOffset, int nameLength) throws IOException {
|
||||||
|
long previousPosition = channel.position();
|
||||||
|
byte[] extra;
|
||||||
|
try {
|
||||||
|
channel.position(headerOffset);
|
||||||
|
validateSignature(inputStream.readInt(), ZipEntry.LOCSIG, "local file");
|
||||||
|
|
||||||
|
// skip to and read the length of the extra field.
|
||||||
|
skip(ZipEntry.LOCEXT - 4);
|
||||||
|
final int extraLength = inputStream.readShort();
|
||||||
|
|
||||||
|
skip(nameLength);
|
||||||
|
extra = inputStream.readBytes(extraLength);
|
||||||
|
} finally {
|
||||||
|
channel.position(previousPosition);
|
||||||
|
}
|
||||||
|
return extra;
|
||||||
|
}
|
||||||
|
private void skip(long n) throws IOException {
|
||||||
|
channel.position(channel.position() + n);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class FilterInputStreamEx extends FilterInputStream {
|
||||||
|
public FilterInputStreamEx(InputStream inputStream) {
|
||||||
|
super(inputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long readInt() throws IOException {
|
||||||
|
final byte[] bytes = readBytes(4);
|
||||||
|
return ((long) (0xff & bytes[3]) << 24) | ((0xff & bytes[2]) << 16) |
|
||||||
|
((0xff & bytes[1]) << 8) | (0xff & bytes[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int readShort() throws IOException {
|
||||||
|
final byte[] bytes = readBytes(2);
|
||||||
|
return (((0xff & bytes[1]) << 8) | (bytes[0] & 0xff));
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] readBytes(int n) throws IOException {
|
||||||
|
final byte[] bytes = new byte[n];
|
||||||
|
read(bytes);
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -15,22 +15,89 @@
|
|||||||
*/
|
*/
|
||||||
package com.reandroid.archive;
|
package com.reandroid.archive;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.*;
|
||||||
import java.io.InputStream;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.Channels;
|
||||||
|
import java.nio.channels.FileChannel;
|
||||||
|
import java.nio.channels.WritableByteChannel;
|
||||||
|
import java.util.zip.Inflater;
|
||||||
|
import java.util.zip.InflaterInputStream;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipFile;
|
|
||||||
|
|
||||||
public class ZipEntrySource extends InputSource {
|
public class ZipEntrySource extends InputSource {
|
||||||
private final ZipFile zipFile;
|
|
||||||
private final ZipEntry zipEntry;
|
private final ZipEntry zipEntry;
|
||||||
public ZipEntrySource(ZipFile zipFile, ZipEntry zipEntry){
|
private final FileChannel channel;
|
||||||
|
private final long headerOffset;
|
||||||
|
private long dataOffset;
|
||||||
|
|
||||||
|
public ZipEntrySource(ZipEntry zipEntry, long headerOffset, FileChannel channel) {
|
||||||
super(zipEntry.getName());
|
super(zipEntry.getName());
|
||||||
this.zipFile=zipFile;
|
this.zipEntry = zipEntry;
|
||||||
this.zipEntry=zipEntry;
|
|
||||||
super.setMethod(zipEntry.getMethod());
|
super.setMethod(zipEntry.getMethod());
|
||||||
|
this.channel = channel;
|
||||||
|
this.headerOffset = headerOffset;
|
||||||
|
this.dataOffset = ZipDeserializer.dataOffset(headerOffset, zipEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getLength() {
|
||||||
|
return zipEntry.getSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getCrc() {
|
||||||
|
return zipEntry.getCrc();
|
||||||
|
}
|
||||||
|
|
||||||
|
private InputStream rawStream() {
|
||||||
|
return new InputStream() {
|
||||||
|
private int cursor = 0;
|
||||||
|
@Override
|
||||||
|
public int read(byte[] b, int off, int len) throws IOException {
|
||||||
|
long remaining = zipEntry.getCompressedSize() - cursor;
|
||||||
|
|
||||||
|
if (len == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (len < 0 || off < 0 || (len + off) < b.length) {
|
||||||
|
throw new IOException("Invalid length/offset.");
|
||||||
|
}
|
||||||
|
if (remaining == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
len = (int) Math.min(remaining, len);
|
||||||
|
int n = channel.read(ByteBuffer.wrap(b).position(off).limit(off + len), dataOffset + cursor);
|
||||||
|
cursor += n;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
byte[] b = new byte[1];
|
||||||
|
int n = read(b);
|
||||||
|
if (n == 1)
|
||||||
|
return b[0] & 0xff;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InputStream openStream() throws IOException {
|
public InputStream openStream() throws IOException {
|
||||||
return zipFile.getInputStream(zipEntry);
|
InputStream stream = rawStream();
|
||||||
|
switch (zipEntry.getMethod()) {
|
||||||
|
case ZipEntry.DEFLATED:
|
||||||
|
return new InflaterInputStream(stream, new Inflater(true));
|
||||||
|
case ZipEntry.STORED:
|
||||||
|
return stream;
|
||||||
|
default:
|
||||||
|
throw new IOException("Unsupported compression method: " + zipEntry.getMethod());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public WriteCompressedResult writeCompressed(OutputStream outputStream) throws IOException {
|
||||||
|
write(outputStream, rawStream());
|
||||||
|
return new WriteCompressedResult(getCrc(), zipEntry.getCompressedSize(), getLength());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,20 +12,81 @@
|
|||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* This class contains code from "apksigner" and I couldn't find the
|
||||||
|
* original repo/author to credit here.
|
||||||
*/
|
*/
|
||||||
package com.reandroid.archive;
|
package com.reandroid.archive;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.util.List;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.*;
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.*;
|
||||||
|
|
||||||
|
public class ZipSerializer {
|
||||||
|
public static final int ZIP_ENTRY_VERSION = 20;
|
||||||
|
public static final int ZIP_ENTRY_USES_DATA_DESCR = 0x0008;
|
||||||
|
|
||||||
|
private static class XEntry {
|
||||||
|
public final ZipEntry entry;
|
||||||
|
public final long headerOffset;
|
||||||
|
public final int flags;
|
||||||
|
public final int padding;
|
||||||
|
|
||||||
|
public XEntry(ZipEntry entry, long headerOffset, int flags, int padding) {
|
||||||
|
this.entry = entry;
|
||||||
|
this.headerOffset = headerOffset;
|
||||||
|
this.flags = flags;
|
||||||
|
this.padding = padding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class FilterOutputStreamEx extends FilterOutputStream {
|
||||||
|
private long totalWritten = 0;
|
||||||
|
public FilterOutputStreamEx(OutputStream out) {
|
||||||
|
super(out);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void write(byte[] b) throws IOException {
|
||||||
|
out.write(b);
|
||||||
|
totalWritten += b.length;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void write(byte[] b, int off, int len) throws IOException {
|
||||||
|
out.write(b, off, len);
|
||||||
|
totalWritten += len;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void write(int b) throws IOException {
|
||||||
|
out.write(b);
|
||||||
|
totalWritten += 1;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
super.close();
|
||||||
|
}
|
||||||
|
public void writeInt(long v) throws IOException {
|
||||||
|
write((int) (v & 0xff));
|
||||||
|
write((int) ((v >>> 8) & 0xff));
|
||||||
|
write((int) ((v >>> 16) & 0xff));
|
||||||
|
write((int) ((v >>> 24) & 0xff));
|
||||||
|
}
|
||||||
|
public void writeShort(int v) throws IOException {
|
||||||
|
write((v) & 0xff);
|
||||||
|
write((v >>> 8) & 0xff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class ZipSerializer {
|
|
||||||
private final List<InputSource> mSourceList;
|
private final List<InputSource> mSourceList;
|
||||||
|
private final List<XEntry> mEntries = new ArrayList<>();
|
||||||
|
private final ZipAlign zipAlign;
|
||||||
private WriteProgress writeProgress;
|
private WriteProgress writeProgress;
|
||||||
private WriteInterceptor writeInterceptor;
|
private WriteInterceptor writeInterceptor;
|
||||||
public ZipSerializer(List<InputSource> sourceList){
|
public ZipSerializer(List<InputSource> sourceList, ZipAlign zipAlign){
|
||||||
this.mSourceList=sourceList;
|
this.mSourceList=sourceList;
|
||||||
|
this.zipAlign = zipAlign;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setWriteInterceptor(WriteInterceptor writeInterceptor) {
|
public void setWriteInterceptor(WriteInterceptor writeInterceptor) {
|
||||||
@ -40,9 +101,9 @@ public class ZipSerializer {
|
|||||||
dir.mkdirs();
|
dir.mkdirs();
|
||||||
}
|
}
|
||||||
File tmp=toTmpFile(outZip);
|
File tmp=toTmpFile(outZip);
|
||||||
FileOutputStream fileOutputStream=new FileOutputStream(tmp);
|
OutputStream outputStream=new BufferedOutputStream(new FileOutputStream(tmp), 32 * 1024);
|
||||||
long length= writeZip(fileOutputStream);
|
long length= writeZip(outputStream);
|
||||||
fileOutputStream.close();
|
outputStream.close();
|
||||||
outZip.delete();
|
outZip.delete();
|
||||||
tmp.renameTo(outZip);
|
tmp.renameTo(outZip);
|
||||||
return length;
|
return length;
|
||||||
@ -53,41 +114,185 @@ public class ZipSerializer {
|
|||||||
return new File(dir, name);
|
return new File(dir, name);
|
||||||
}
|
}
|
||||||
public long writeZip(OutputStream outputStream) throws IOException{
|
public long writeZip(OutputStream outputStream) throws IOException{
|
||||||
long length=0;
|
|
||||||
WriteProgress progress=writeProgress;
|
WriteProgress progress=writeProgress;
|
||||||
ZipOutputStream zipOutputStream=new ZipOutputStream(outputStream);
|
FilterOutputStreamEx zipOutputStream=new FilterOutputStreamEx(outputStream);
|
||||||
for(InputSource inputSource:mSourceList){
|
for(InputSource inputSource:mSourceList){
|
||||||
inputSource = interceptWrite(inputSource);
|
inputSource = interceptWrite(inputSource);
|
||||||
if(inputSource==null){
|
if(inputSource==null){
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if(progress!=null){
|
if(progress!=null){
|
||||||
progress.onCompressFile(inputSource.getAlias(), inputSource.getMethod(), length);
|
progress.onCompressFile(inputSource.getAlias(), inputSource.getMethod(), zipOutputStream.totalWritten);
|
||||||
}
|
}
|
||||||
length+=write(zipOutputStream, inputSource);
|
write(zipOutputStream, inputSource);
|
||||||
zipOutputStream.closeEntry();
|
|
||||||
inputSource.disposeInputSource();
|
inputSource.disposeInputSource();
|
||||||
}
|
}
|
||||||
|
buildCentralDirectory(zipOutputStream);
|
||||||
zipOutputStream.close();
|
zipOutputStream.close();
|
||||||
return length;
|
return zipOutputStream.totalWritten;
|
||||||
}
|
}
|
||||||
private long write(ZipOutputStream zipOutputStream, InputSource inputSource) throws IOException{
|
|
||||||
ZipEntry zipEntry=createZipEntry(inputSource);
|
private void writeDescriptor(FilterOutputStreamEx mOutputStream, ZipEntry entry) throws IOException{
|
||||||
zipOutputStream.putNextEntry(zipEntry);
|
if (entry.getCompressedSize() != -1) {
|
||||||
return inputSource.write(zipOutputStream);
|
mOutputStream.writeInt(entry.getCrc());
|
||||||
|
mOutputStream.writeInt(entry.getCompressedSize());
|
||||||
|
mOutputStream.writeInt(entry.getSize());
|
||||||
|
} else {
|
||||||
|
mOutputStream.write(new byte[12]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void write(FilterOutputStreamEx mOutputStream, InputSource inputSource) throws IOException{
|
||||||
|
final ZipEntry entry=createZipEntry(inputSource);
|
||||||
|
|
||||||
|
int flags = 1 << 11;
|
||||||
|
if (entry.getMethod() != ZipEntry.STORED) {
|
||||||
|
flags |= ZIP_ENTRY_USES_DATA_DESCR;
|
||||||
|
}
|
||||||
|
|
||||||
|
final long outputEntryHeaderOffset = mOutputStream.totalWritten;
|
||||||
|
final long outputEntryDataOffset = ZipDeserializer.dataOffset(outputEntryHeaderOffset, entry);
|
||||||
|
int padding = 0;
|
||||||
|
if (zipAlign != null) {
|
||||||
|
padding = zipAlign.alignEntry(entry, outputEntryDataOffset);
|
||||||
|
}
|
||||||
|
if (entry.getExtra() == null) {
|
||||||
|
entry.setExtra(new byte[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
final XEntry xentry = new XEntry(entry, outputEntryHeaderOffset, flags, padding);
|
||||||
|
mEntries.add(xentry);
|
||||||
|
|
||||||
|
mOutputStream.writeInt(ZipOutputStream.LOCSIG);
|
||||||
|
mOutputStream.writeShort(ZIP_ENTRY_VERSION);
|
||||||
|
mOutputStream.writeShort(flags);
|
||||||
|
mOutputStream.writeShort(entry.getMethod());
|
||||||
|
|
||||||
|
int modDate;
|
||||||
|
int time;
|
||||||
|
GregorianCalendar cal = new GregorianCalendar();
|
||||||
|
cal.setTime(new Date(entry.getTime()));
|
||||||
|
int year = cal.get(Calendar.YEAR);
|
||||||
|
if (year < 1980) {
|
||||||
|
modDate = 0x21;
|
||||||
|
time = 0;
|
||||||
|
} else {
|
||||||
|
modDate = cal.get(Calendar.DATE);
|
||||||
|
modDate = (cal.get(Calendar.MONTH) + 1 << 5) | modDate;
|
||||||
|
modDate = ((cal.get(Calendar.YEAR) - 1980) << 9) | modDate;
|
||||||
|
time = cal.get(Calendar.SECOND) >> 1;
|
||||||
|
time = (cal.get(Calendar.MINUTE) << 5) | time;
|
||||||
|
time = (cal.get(Calendar.HOUR_OF_DAY) << 11) | time;
|
||||||
|
}
|
||||||
|
|
||||||
|
mOutputStream.writeShort(time);
|
||||||
|
mOutputStream.writeShort(modDate);
|
||||||
|
|
||||||
|
writeDescriptor(mOutputStream, entry);
|
||||||
|
|
||||||
|
mOutputStream.writeShort(entry.getName().getBytes(StandardCharsets.UTF_8).length);
|
||||||
|
mOutputStream.writeShort(entry.getExtra().length);
|
||||||
|
mOutputStream.write(entry.getName().getBytes(StandardCharsets.UTF_8));
|
||||||
|
mOutputStream.write(entry.getExtra());
|
||||||
|
|
||||||
|
if (entry.getMethod() == ZipEntry.STORED) {
|
||||||
|
inputSource.write(mOutputStream);
|
||||||
|
} else {
|
||||||
|
InputSource.WriteCompressedResult result = inputSource.writeCompressed(mOutputStream);
|
||||||
|
entry.setCompressedSize(result.compressedSize);
|
||||||
|
entry.setSize(result.uncompressedSize);
|
||||||
|
entry.setCrc(result.crc);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((flags & ZIP_ENTRY_USES_DATA_DESCR) != 0) {
|
||||||
|
mOutputStream.writeInt(ZipEntry.EXTSIG);
|
||||||
|
writeDescriptor(mOutputStream, entry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
private ZipEntry createZipEntry(InputSource inputSource) throws IOException {
|
private ZipEntry createZipEntry(InputSource inputSource) throws IOException {
|
||||||
String name=inputSource.getAlias();
|
String name=inputSource.getAlias();
|
||||||
ZipEntry zipEntry=new ZipEntry(name);
|
ZipEntry zipEntry=new ZipEntry(name);
|
||||||
|
|
||||||
int method = inputSource.getMethod();
|
int method = inputSource.getMethod();
|
||||||
zipEntry.setMethod(method);
|
zipEntry.setMethod(method);
|
||||||
if(method==ZipEntry.STORED){
|
if (method == ZipEntry.STORED) {
|
||||||
|
long length = inputSource.getLength();
|
||||||
zipEntry.setCrc(inputSource.getCrc());
|
zipEntry.setCrc(inputSource.getCrc());
|
||||||
zipEntry.setSize(inputSource.getLength());
|
zipEntry.setSize(length);
|
||||||
|
zipEntry.setCompressedSize(length);
|
||||||
}
|
}
|
||||||
return zipEntry;
|
return zipEntry;
|
||||||
}
|
}
|
||||||
private InputSource interceptWrite(InputSource inputSource){
|
private void buildCentralDirectory(FilterOutputStreamEx mOutputStream) throws IOException {
|
||||||
|
final long centralDirOffset = mOutputStream.totalWritten;
|
||||||
|
final int entryCount = mEntries.size();
|
||||||
|
for (int i = 0; i < entryCount; i++) {
|
||||||
|
XEntry xentry = mEntries.get(i);
|
||||||
|
final ZipEntry entry = xentry.entry;
|
||||||
|
int modDate;
|
||||||
|
int time;
|
||||||
|
GregorianCalendar cal = new GregorianCalendar();
|
||||||
|
cal.setTime(new Date(entry.getTime()));
|
||||||
|
int year = cal.get(Calendar.YEAR);
|
||||||
|
if (year < 1980) {
|
||||||
|
modDate = 0x21;
|
||||||
|
time = 0;
|
||||||
|
} else {
|
||||||
|
modDate = cal.get(Calendar.DATE);
|
||||||
|
modDate = (cal.get(Calendar.MONTH) + 1 << 5) | modDate;
|
||||||
|
modDate = ((cal.get(Calendar.YEAR) - 1980) << 9) | modDate;
|
||||||
|
time = cal.get(Calendar.SECOND) >> 1;
|
||||||
|
time = (cal.get(Calendar.MINUTE) << 5) | time;
|
||||||
|
time = (cal.get(Calendar.HOUR_OF_DAY) << 11) | time;
|
||||||
|
}
|
||||||
|
|
||||||
|
mOutputStream.writeInt(ZipFile.CENSIG); // CEN header signature
|
||||||
|
mOutputStream.writeShort(ZIP_ENTRY_VERSION); // version made by
|
||||||
|
mOutputStream.writeShort(ZIP_ENTRY_VERSION); // version needed to extract
|
||||||
|
mOutputStream.writeShort(xentry.flags); // general purpose bit flag
|
||||||
|
mOutputStream.writeShort(entry.getMethod()); // compression method
|
||||||
|
mOutputStream.writeShort(time);
|
||||||
|
mOutputStream.writeShort(modDate);
|
||||||
|
mOutputStream.writeInt(entry.getCrc()); // crc-32
|
||||||
|
mOutputStream.writeInt(entry.getCompressedSize()); // compressed size
|
||||||
|
mOutputStream.writeInt(entry.getSize()); // uncompressed size
|
||||||
|
final byte[] nameBytes = entry.getName().getBytes(StandardCharsets.UTF_8);
|
||||||
|
mOutputStream.writeShort(nameBytes.length);
|
||||||
|
mOutputStream.writeShort(entry.getExtra() != null ? entry.getExtra().length - xentry.padding : 0);
|
||||||
|
final byte[] commentBytes;
|
||||||
|
if (entry.getComment() != null) {
|
||||||
|
commentBytes = entry.getComment().getBytes(StandardCharsets.UTF_8);
|
||||||
|
mOutputStream.writeShort(Math.min(commentBytes.length, 0xffff));
|
||||||
|
} else {
|
||||||
|
commentBytes = null;
|
||||||
|
mOutputStream.writeShort(0);
|
||||||
|
}
|
||||||
|
mOutputStream.writeShort(0); // starting disk number
|
||||||
|
mOutputStream.writeShort(0); // internal file attributes (unused)
|
||||||
|
mOutputStream.writeInt(0); // external file attributes (unused)
|
||||||
|
mOutputStream.writeInt(xentry.headerOffset); // relative offset of local header
|
||||||
|
mOutputStream.write(nameBytes);
|
||||||
|
if (entry.getExtra() != null) {
|
||||||
|
mOutputStream.write(entry.getExtra(), 0, entry.getExtra().length - xentry.padding);
|
||||||
|
}
|
||||||
|
if (commentBytes != null) {
|
||||||
|
mOutputStream.write(commentBytes, 0, Math.min(commentBytes.length, 0xffff));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final long centralDirSize = mOutputStream.totalWritten - centralDirOffset;
|
||||||
|
|
||||||
|
mOutputStream.writeInt(ZipFile.ENDSIG); // END record signature
|
||||||
|
mOutputStream.writeShort(0); // number of this disk
|
||||||
|
mOutputStream.writeShort(0); // central directory start disk
|
||||||
|
mOutputStream.writeShort(entryCount); // number of directory entries on disk
|
||||||
|
mOutputStream.writeShort(entryCount); // total number of directory entries
|
||||||
|
mOutputStream.writeInt(centralDirSize); // length of central directory
|
||||||
|
mOutputStream.writeInt(centralDirOffset); // offset of central directory
|
||||||
|
mOutputStream.writeShort(0);
|
||||||
|
mOutputStream.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
private InputSource interceptWrite(InputSource inputSource){
|
||||||
WriteInterceptor interceptor=writeInterceptor;
|
WriteInterceptor interceptor=writeInterceptor;
|
||||||
if(interceptor!=null){
|
if(interceptor!=null){
|
||||||
return interceptor.onWriteArchive(inputSource);
|
return interceptor.onWriteArchive(inputSource);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user