diff --git a/src/main/java/com/reandroid/apk/ApkModule.java b/src/main/java/com/reandroid/apk/ApkModule.java index bb8b64f..7abf08b 100644 --- a/src/main/java/com/reandroid/apk/ApkModule.java +++ b/src/main/java/com/reandroid/apk/ApkModule.java @@ -16,6 +16,8 @@ package com.reandroid.apk; import com.reandroid.archive.*; +import com.reandroid.archive2.Archive; +import com.reandroid.archive2.writter.ApkWriter; import com.reandroid.arsc.ApkFile; import com.reandroid.arsc.array.PackageArray; import com.reandroid.arsc.chunk.Chunk; @@ -268,10 +270,15 @@ public class ApkModule implements ApkFile { if(manifest!=null){ manifest.setSort(0); } + ApkWriter apkWriter = new ApkWriter(file, archive.listInputSources()); + apkWriter.write(); + apkWriter.close(); + /* ZipSerializer serializer=new ZipSerializer(archive.listInputSources()); serializer.setWriteProgress(progress); serializer.setWriteInterceptor(interceptor); serializer.writeZip(file); + */ } private void uncompressNonXmlResFiles() { for(ResFile resFile:listResFiles()){ @@ -725,7 +732,7 @@ public class ApkModule implements ApkFile { return loadApkFile(apkFile, ApkUtil.DEF_MODULE_NAME); } public static ApkModule loadApkFile(File apkFile, String moduleName) throws IOException { - APKArchive archive=APKArchive.loadZippedApk(apkFile); - return new ApkModule(moduleName, archive); + Archive archive = new Archive(apkFile); + return new ApkModule(moduleName, archive.createAPKArchive()); } } diff --git a/src/main/java/com/reandroid/archive/InputSource.java b/src/main/java/com/reandroid/archive/InputSource.java index 407a001..0d9362a 100644 --- a/src/main/java/com/reandroid/archive/InputSource.java +++ b/src/main/java/com/reandroid/archive/InputSource.java @@ -64,7 +64,7 @@ public abstract class InputSource { } private long write(OutputStream outputStream, InputStream inputStream) throws IOException { long result=0; - byte[] buffer=new byte[10240]; + byte[] buffer=new byte[1024 * 1000]; int len; while ((len=inputStream.read(buffer))>0){ outputStream.write(buffer, 0, len); diff --git a/src/main/java/com/reandroid/archive2/Archive.java b/src/main/java/com/reandroid/archive2/Archive.java index 9942edb..3af1760 100644 --- a/src/main/java/com/reandroid/archive2/Archive.java +++ b/src/main/java/com/reandroid/archive2/Archive.java @@ -15,10 +15,13 @@ */ package com.reandroid.archive2; +import com.reandroid.archive.APKArchive; +import com.reandroid.archive.InputSource; import com.reandroid.archive2.block.*; -import com.reandroid.archive2.io.ArchiveFile; +import com.reandroid.archive2.io.ArchiveEntrySource; +import com.reandroid.archive2.io.ZipFileInput; import com.reandroid.archive2.io.ArchiveUtil; -import com.reandroid.archive2.io.ZipSource; +import com.reandroid.archive2.io.ZipInput; import com.reandroid.archive2.model.LocalFileDirectory; import java.io.File; @@ -26,23 +29,25 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.zip.Inflater; import java.util.zip.InflaterInputStream; import java.util.zip.ZipEntry; public class Archive { - private final ZipSource zipSource; + private final ZipInput zipInput; private final List entryList; private final EndRecord endRecord; private final ApkSignatureBlock apkSignatureBlock; - public Archive(ZipSource zipSource) throws IOException { - this.zipSource = zipSource; + public Archive(ZipInput zipInput) throws IOException { + this.zipInput = zipInput; LocalFileDirectory lfd = new LocalFileDirectory(); - lfd.visit(zipSource); + lfd.visit(zipInput); List localFileHeaderList = lfd.getHeaderList(); List centralEntryHeaderList = lfd.getCentralFileDirectory().getHeaderList(); - List entryList = new ArrayList<>(); + List entryList = new ArrayList<>(localFileHeaderList.size()); for(int i=0;i mapEntrySource(){ + Map map = new LinkedHashMap<>(); + ZipInput zipInput = this.zipInput; + List entryList = this.entryList; + for(int i=0; i extends InputStream { + private final T inputStream; + private final CRC32 crc; + private long size; + private long mCheckSum; + private boolean mFinished; + public CountingInputStream(T inputStream, boolean disableCrc){ + this.inputStream = inputStream; + CRC32 crc32; + if(disableCrc){ + crc32 = null; + }else { + crc32 = new CRC32(); + } + this.crc = crc32; + } + public CountingInputStream(T inputStream){ + this(inputStream, false); + } + public T getInputStream() { + return inputStream; + } + public long getSize() { + return size; + } + public long getCrc() { + return mCheckSum; + } + @Override + public int read(byte[] bytes, int offset, int length) throws IOException{ + if(mFinished){ + return -1; + } + length = inputStream.read(bytes, offset, length); + if(length < 0){ + onFinished(); + return length; + } + this.size += length; + if(this.crc != null){ + this.crc.update(bytes, offset, length); + } + return length; + } + @Override + public int read(byte[] bytes) throws IOException{ + return this.read(bytes, 0, bytes.length); + } + @Override + public int read() throws IOException { + throw new IOException("Why one byte ?"); + } + @Override + public long skip(long amount) throws IOException { + if(mFinished){ + return 0; + } + if(amount <= 0){ + return amount; + } + InputStream inputStream = this.inputStream; + if(inputStream instanceof CountingInputStream){ + return inputStream.skip(amount); + } + long remaining = amount; + int len = 1024 * 1000; + if(remaining < len){ + len = (int) remaining; + } + final byte[] buffer = new byte[len]; + int read; + while (true){ + read = inputStream.read(buffer, 0, len); + if(read < 0){ + onFinished(); + break; + } + remaining = remaining - read; + if(remaining <= 0){ + break; + } + if(remaining < len){ + len = (int) remaining; + } + } + return amount - remaining; + } + @Override + public void close() throws IOException{ + if(!mFinished){ + onFinished(); + } + inputStream.close(); + } + private void onFinished(){ + this.mFinished = true; + if(this.crc!=null){ + this.mCheckSum = this.crc.getValue(); + } + } + @Override + public String toString(){ + if(!mFinished || crc==null){ + return "[" + size + "]: " + inputStream.getClass().getSimpleName(); + } + return "[size=" + size +", crc=" + String.format("0x%08x", mCheckSum) + "]: " + inputStream.getClass().getSimpleName(); + } +} diff --git a/src/main/java/com/reandroid/archive2/io/CountingOutputStream.java b/src/main/java/com/reandroid/archive2/io/CountingOutputStream.java new file mode 100644 index 0000000..0ae2a2b --- /dev/null +++ b/src/main/java/com/reandroid/archive2/io/CountingOutputStream.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2022 github.com/REAndroid + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.reandroid.archive2.io; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.zip.CRC32; + +public class CountingOutputStream extends OutputStream { + private final T outputStream; + private CRC32 crc; + private long size; + public CountingOutputStream(T outputStream, boolean disableCrc){ + this.outputStream = outputStream; + CRC32 crc32; + if(disableCrc){ + crc32 = null; + }else { + crc32 = new CRC32(); + } + this.crc = crc32; + } + public CountingOutputStream(T outputStream){ + this(outputStream, false); + } + + public void disableCrc(boolean disableCrc) { + if(!disableCrc){ + if(crc == null){ + this.crc = new CRC32(); + } + }else{ + this.crc = null; + } + } + + public void reset(){ + this.crc = new CRC32(); + this.size = 0L; + } + public T getOutputStream() { + return outputStream; + } + public long getSize() { + return size; + } + public long getCrc() { + if(crc != null){ + return crc.getValue(); + } + return 0; + } + @Override + public void write(byte[] bytes, int offset, int length) throws IOException{ + if(length == 0){ + return; + } + outputStream.write(bytes, offset, length); + this.size += length; + if(this.crc != null){ + this.crc.update(bytes, offset, length); + } + } + @Override + public void write(byte[] bytes) throws IOException{ + this.write(bytes, 0, bytes.length); + } + @Override + public void write(int i) throws IOException { + this.write(new byte[]{(byte) i}, 0, 1); + } + @Override + public void close() throws IOException{ + outputStream.close(); + } + @Override + public void flush() throws IOException { + outputStream.flush(); + } + @Override + public String toString(){ + return "[" + size + "]: " + outputStream.getClass().getSimpleName(); + } +} diff --git a/src/main/java/com/reandroid/archive2/io/FileChannelInputStream.java b/src/main/java/com/reandroid/archive2/io/FileChannelInputStream.java new file mode 100644 index 0000000..a509e29 --- /dev/null +++ b/src/main/java/com/reandroid/archive2/io/FileChannelInputStream.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2022 github.com/REAndroid + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.reandroid.archive2.io; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; + +public class FileChannelInputStream extends InputStream { + private final FileChannel fileChannel; + private final long length; + private long total; + private final byte[] buffer; + private int pos; + private int bufferLength; + + public FileChannelInputStream(FileChannel fileChannel, long length){ + this.fileChannel = fileChannel; + this.length = length; + int len = 1024 * 1000 * 100; + if(length < len){ + len = (int) length; + } + this.buffer = new byte[len]; + this.bufferLength = len; + this.pos = len; + } + @Override + public int read(byte[] bytes) throws IOException { + return read(bytes, 0, bytes.length); + } + @Override + public int read(byte[] bytes, int offset, int length) throws IOException { + if(isFinished()){ + return -1; + } + if(length==0){ + return 0; + } + loadBuffer(); + int result = 0; + int read = readBuffer(bytes, offset, length); + result += read; + length = length - read; + offset = offset + read; + while (length>0 && !isFinished()){ + loadBuffer(); + read = readBuffer(bytes, offset, length); + result += read; + length = length - read; + offset = offset + read; + } + return result; + } + private int readBuffer(byte[] bytes, int offset, int length){ + int avail = bufferLength - pos; + if(avail == 0){ + return 0; + } + int read = length; + if(read > avail){ + read = avail; + } + System.arraycopy(buffer, pos, bytes, offset, read); + pos += read; + total += read; + return read; + } + private void loadBuffer() throws IOException { + byte[] buffer = this.buffer; + if(this.pos < buffer.length){ + return; + } + ByteBuffer byteBuffer = ByteBuffer.wrap(buffer); + bufferLength = fileChannel.read(byteBuffer); + pos = 0; + } + private boolean isFinished(){ + return total >= length; + } + @Override + public int read() throws IOException { + throw new IOException("Why one byte?"); + } +} diff --git a/src/main/java/com/reandroid/archive2/io/FileChannelOutputStream.java b/src/main/java/com/reandroid/archive2/io/FileChannelOutputStream.java new file mode 100644 index 0000000..81411e2 --- /dev/null +++ b/src/main/java/com/reandroid/archive2/io/FileChannelOutputStream.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2022 github.com/REAndroid + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.reandroid.archive2.io; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; + +public class FileChannelOutputStream extends OutputStream { + private final FileChannel fileChannel; + public FileChannelOutputStream(FileChannel fileChannel){ + this.fileChannel = fileChannel; + } + @Override + public void write(byte[] bytes) throws IOException { + write(bytes, 0, bytes.length); + } + @Override + public void write(byte[] bytes, int offset, int length) throws IOException { + long position = fileChannel.position(); + length = fileChannel.write(ByteBuffer.wrap(bytes, offset, length)); + fileChannel.position(position + length); + } + @Override + public void write(int i) throws IOException { + byte b = (byte) (i & 0xff); + write(new byte[]{b}); + } + @Override + public void close(){ + + } +} diff --git a/src/main/java/com/reandroid/archive2/io/RandomStream.java b/src/main/java/com/reandroid/archive2/io/RandomStream.java new file mode 100644 index 0000000..62a8e47 --- /dev/null +++ b/src/main/java/com/reandroid/archive2/io/RandomStream.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 github.com/REAndroid + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.reandroid.archive2.io; + +import java.io.IOException; +import java.nio.channels.Channel; +import java.nio.channels.FileChannel; + +public interface RandomStream extends Channel { + long position() throws IOException; + void position(long pos) throws IOException; + @Override + void close() throws IOException; + @Override + boolean isOpen(); + FileChannel getFileChannel() throws IOException; +} diff --git a/src/main/java/com/reandroid/archive2/io/ReadOnlyStream.java b/src/main/java/com/reandroid/archive2/io/ReadOnlyStream.java new file mode 100644 index 0000000..6b89547 --- /dev/null +++ b/src/main/java/com/reandroid/archive2/io/ReadOnlyStream.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2022 github.com/REAndroid + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.reandroid.archive2.io; + +import java.io.IOException; +import java.io.InputStream; + +public interface ReadOnlyStream extends RandomStream{ + long getLength() throws IOException; + InputStream getInputStream(long offset, long length) throws IOException; +} diff --git a/src/main/java/com/reandroid/archive2/io/WriteOnlyStream.java b/src/main/java/com/reandroid/archive2/io/WriteOnlyStream.java new file mode 100644 index 0000000..f4f78f6 --- /dev/null +++ b/src/main/java/com/reandroid/archive2/io/WriteOnlyStream.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2022 github.com/REAndroid + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.reandroid.archive2.io; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public interface WriteOnlyStream extends RandomStream{ + void write(ReadOnlyStream readStream, long length) throws IOException; + void write(InputStream inputStream) throws IOException; + OutputStream getOutputStream() throws IOException; +} diff --git a/src/main/java/com/reandroid/archive2/io/ArchiveFile.java b/src/main/java/com/reandroid/archive2/io/ZipFileInput.java similarity index 71% rename from src/main/java/com/reandroid/archive2/io/ArchiveFile.java rename to src/main/java/com/reandroid/archive2/io/ZipFileInput.java index 37a73ee..bacc29b 100644 --- a/src/main/java/com/reandroid/archive2/io/ArchiveFile.java +++ b/src/main/java/com/reandroid/archive2/io/ZipFileInput.java @@ -20,17 +20,39 @@ import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.file.StandardOpenOption; -public class ArchiveFile extends ZipSource{ +public class ZipFileInput extends ZipInput { private final File file; private FileChannel fileChannel; - private SlicedInputStream mCurrentInputStream; - public ArchiveFile(File file){ + private InputStream mCurrentInputStream; + public ZipFileInput(File file){ this.file = file; } + + @Override + public long position() throws IOException { + FileChannel fileChannel = this.fileChannel; + if(fileChannel != null){ + return fileChannel.position(); + } + return 0; + } + @Override + public void position(long pos) throws IOException { + getFileChannel().position(pos); + } @Override public long getLength(){ return this.file.length(); } + @Override + public InputStream getInputStream(long offset, long length) throws IOException { + closeCurrentInputStream(); + FileChannel fileChannel = getFileChannel(); + fileChannel.position(offset); + mCurrentInputStream = new FileChannelInputStream(fileChannel, length); + return mCurrentInputStream; + } + @Override public byte[] getFooter(int minLength) throws IOException { long position = getLength(); @@ -45,16 +67,7 @@ public class ArchiveFile extends ZipSource{ return buffer.array(); } @Override - public InputStream getInputStream(long offset, long length) throws IOException { - close(); - mCurrentInputStream = new SlicedInputStream(new FileInputStream(this.file), offset, length); - return mCurrentInputStream; - } - @Override - public OutputStream getOutputStream(long offset) throws IOException { - return null; - } - private FileChannel getFileChannel() throws IOException { + public FileChannel getFileChannel() throws IOException { FileChannel fileChannel = this.fileChannel; if(fileChannel != null){ return fileChannel; @@ -67,8 +80,18 @@ public class ArchiveFile extends ZipSource{ } @Override public void close() throws IOException { - closeChannel(); closeCurrentInputStream(); + closeChannel(); + } + @Override + public boolean isOpen(){ + FileChannel fileChannel = this.fileChannel; + if(fileChannel == null){ + return false; + } + synchronized (this){ + return fileChannel.isOpen(); + } } private void closeChannel() throws IOException { FileChannel fileChannel = this.fileChannel; @@ -81,7 +104,7 @@ public class ArchiveFile extends ZipSource{ } } private void closeCurrentInputStream() throws IOException { - SlicedInputStream current = this.mCurrentInputStream; + InputStream current = this.mCurrentInputStream; if(current == null){ return; } diff --git a/src/main/java/com/reandroid/archive2/io/ZipFileOutput.java b/src/main/java/com/reandroid/archive2/io/ZipFileOutput.java new file mode 100644 index 0000000..5640795 --- /dev/null +++ b/src/main/java/com/reandroid/archive2/io/ZipFileOutput.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2022 github.com/REAndroid + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.reandroid.archive2.io; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.file.StandardOpenOption; + +public class ZipFileOutput extends ZipOutput{ + private final File file; + private FileChannel fileChannel; + private FileChannelOutputStream outputStream; + public ZipFileOutput(File file) throws IOException { + initFile(file); + this.file = file; + } + public File getFile() { + return file; + } + public void write(FileChannel input, long length) throws IOException{ + FileChannel fileChannel = getFileChannel(); + long pos = fileChannel.position(); + length = fileChannel.transferFrom(input, pos, length); + fileChannel.position(pos + length); + } + + @Override + public long position() throws IOException { + return getFileChannel().position(); + } + @Override + public void position(long pos) throws IOException { + getFileChannel().position(pos); + } + @Override + public void close() throws IOException { + FileChannel fileChannel = this.fileChannel; + if(fileChannel != null){ + fileChannel.close(); + } + } + @Override + public boolean isOpen() { + FileChannel fileChannel = this.fileChannel; + if(fileChannel != null){ + return fileChannel.isOpen(); + } + return false; + } + @Override + public FileChannel getFileChannel() throws IOException { + FileChannel fileChannel = this.fileChannel; + if(fileChannel != null){ + return fileChannel; + } + synchronized (this){ + fileChannel = FileChannel.open(this.file.toPath(), StandardOpenOption.WRITE); + this.fileChannel = fileChannel; + return fileChannel; + } + } + @Override + public void write(ReadOnlyStream readStream, long length) throws IOException { + FileChannel input = readStream.getFileChannel(); + if(input != null){ + write(input, length); + return; + } + write(readStream.getInputStream(readStream.position(), length)); + } + @Override + public void write(InputStream inputStream) throws IOException { + FileChannel fileChannel = getFileChannel(); + long pos = fileChannel.position(); + int bufferLength = 1024 * 1000 * 100; + byte[] buffer = new byte[bufferLength]; + long result = 0; + int read; + while ((read = inputStream.read(buffer, 0, bufferLength)) > 0){ + ByteBuffer byteBuffer = ByteBuffer.wrap(buffer, 0, read); + fileChannel.write(byteBuffer); + result += read; + } + inputStream.close(); + fileChannel.position(pos + result); + } + @Override + public FileChannelOutputStream getOutputStream() throws IOException { + FileChannelOutputStream outputStream = this.outputStream; + if(outputStream == null){ + outputStream = new FileChannelOutputStream(getFileChannel()); + this.outputStream = outputStream; + } + return outputStream; + } + + + private static void initFile(File file) throws IOException{ + if(file.isDirectory()){ + throw new IOException("Not file: " + file); + } + File dir = file.getParentFile(); + if(dir != null && !dir.exists()){ + dir.mkdirs(); + } + if(file.exists()){ + file.delete(); + } + file.createNewFile(); + } +} diff --git a/src/main/java/com/reandroid/archive2/io/ZipSource.java b/src/main/java/com/reandroid/archive2/io/ZipInput.java similarity index 68% rename from src/main/java/com/reandroid/archive2/io/ZipSource.java rename to src/main/java/com/reandroid/archive2/io/ZipInput.java index 48365bf..080501d 100644 --- a/src/main/java/com/reandroid/archive2/io/ZipSource.java +++ b/src/main/java/com/reandroid/archive2/io/ZipInput.java @@ -15,14 +15,8 @@ */ package com.reandroid.archive2.io; -import java.io.Closeable; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -public abstract class ZipSource implements Closeable { - public abstract long getLength(); +public abstract class ZipInput implements ReadOnlyStream { public abstract byte[] getFooter(int minLength) throws IOException; - public abstract InputStream getInputStream(long offset, long length) throws IOException; - public abstract OutputStream getOutputStream(long offset) throws IOException; } diff --git a/src/main/java/com/reandroid/archive2/io/ZipOutput.java b/src/main/java/com/reandroid/archive2/io/ZipOutput.java new file mode 100644 index 0000000..b925a9b --- /dev/null +++ b/src/main/java/com/reandroid/archive2/io/ZipOutput.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2022 github.com/REAndroid + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.reandroid.archive2.io; + +public abstract class ZipOutput implements WriteOnlyStream{ + +} diff --git a/src/main/java/com/reandroid/archive2/model/CentralFileDirectory.java b/src/main/java/com/reandroid/archive2/model/CentralFileDirectory.java index 7c8a063..e309ba4 100644 --- a/src/main/java/com/reandroid/archive2/model/CentralFileDirectory.java +++ b/src/main/java/com/reandroid/archive2/model/CentralFileDirectory.java @@ -19,7 +19,7 @@ import com.reandroid.archive2.block.CentralEntryHeader; import com.reandroid.archive2.block.EndRecord; import com.reandroid.archive2.block.LocalFileHeader; import com.reandroid.archive2.block.SignatureFooter; -import com.reandroid.archive2.io.ZipSource; +import com.reandroid.archive2.io.ZipInput; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -73,13 +73,13 @@ public class CentralFileDirectory { public EndRecord getEndRecord() { return endRecord; } - public void visit(ZipSource zipSource) throws IOException { - byte[] footer = zipSource.getFooter(SignatureFooter.MIN_SIZE + EndRecord.MAX_LENGTH); + public void visit(ZipInput zipInput) throws IOException { + byte[] footer = zipInput.getFooter(SignatureFooter.MIN_SIZE + EndRecord.MAX_LENGTH); EndRecord endRecord = findEndRecord(footer); int length = (int) endRecord.getLengthOfCentralDirectory(); int endLength = endRecord.countBytes(); if(footer.length < (length + endLength)){ - footer = zipSource.getFooter(SignatureFooter.MIN_SIZE + length + endLength); + footer = zipInput.getFooter(SignatureFooter.MIN_SIZE + length + endLength); } int offset = footer.length - length - endLength; this.endRecord = endRecord; diff --git a/src/main/java/com/reandroid/archive2/model/LocalFileDirectory.java b/src/main/java/com/reandroid/archive2/model/LocalFileDirectory.java index 23375e7..4429339 100644 --- a/src/main/java/com/reandroid/archive2/model/LocalFileDirectory.java +++ b/src/main/java/com/reandroid/archive2/model/LocalFileDirectory.java @@ -17,7 +17,7 @@ package com.reandroid.archive2.model; import com.reandroid.archive2.block.*; import com.reandroid.archive2.block.ApkSignatureBlock; -import com.reandroid.archive2.io.ZipSource; +import com.reandroid.archive2.io.ZipInput; import com.reandroid.arsc.io.BlockReader; import java.io.IOException; @@ -37,14 +37,14 @@ public class LocalFileDirectory { public LocalFileDirectory(){ this(new CentralFileDirectory()); } - public void visit(ZipSource zipSource) throws IOException { - getCentralFileDirectory().visit(zipSource); - visitLocalFile(zipSource); - visitApkSigBlock(zipSource); + public void visit(ZipInput zipInput) throws IOException { + getCentralFileDirectory().visit(zipInput); + visitLocalFile(zipInput); + visitApkSigBlock(zipInput); } - private void visitLocalFile(ZipSource zipSource) throws IOException { + private void visitLocalFile(ZipInput zipInput) throws IOException { EndRecord endRecord = getCentralFileDirectory().getEndRecord(); - InputStream inputStream = zipSource.getInputStream(0, endRecord.getOffsetOfCentralDirectory()); + InputStream inputStream = zipInput.getInputStream(0, endRecord.getOffsetOfCentralDirectory()); visitLocalFile(inputStream); inputStream.close(); } @@ -81,7 +81,7 @@ public class LocalFileDirectory { } mTotalDataLength = offset; } - private void visitApkSigBlock(ZipSource zipSource) throws IOException{ + private void visitApkSigBlock(ZipInput zipInput) throws IOException{ CentralFileDirectory cfd = getCentralFileDirectory(); SignatureFooter footer = cfd.getSignatureFooter(); if(footer == null || !footer.isValid()){ @@ -91,7 +91,7 @@ public class LocalFileDirectory { long length = footer.getSignatureSize() + 8; long offset = endRecord.getOffsetOfCentralDirectory() - length; ApkSignatureBlock apkSignatureBlock = new ApkSignatureBlock(footer); - apkSignatureBlock.readBytes(new BlockReader(zipSource.getInputStream(offset, length))); + apkSignatureBlock.readBytes(new BlockReader(zipInput.getInputStream(offset, length))); this.apkSignatureBlock = apkSignatureBlock; } public ApkSignatureBlock getApkSigBlock() { diff --git a/src/main/java/com/reandroid/archive2/writter/ApkWriter.java b/src/main/java/com/reandroid/archive2/writter/ApkWriter.java new file mode 100644 index 0000000..51b1756 --- /dev/null +++ b/src/main/java/com/reandroid/archive2/writter/ApkWriter.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2022 github.com/REAndroid + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.reandroid.archive2.writter; + +import com.reandroid.apk.RenamedInputSource; +import com.reandroid.archive.InputSource; +import com.reandroid.archive2.ZipSignature; +import com.reandroid.archive2.block.EndRecord; +import com.reandroid.archive2.io.ArchiveEntrySource; +import com.reandroid.archive2.io.ZipFileOutput; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class ApkWriter extends ZipFileOutput { + private final Collection sourceList; + public ApkWriter(File file, Collection sourceList) throws IOException { + super(file); + this.sourceList = sourceList; + } + public void write()throws IOException { + List outputList = buildOutputEntry(); + BufferFileInput buffer = writeBuffer(outputList); + buffer.unlock(); + ZipAligner aligner = new ZipAligner(); + align(aligner, outputList); + writeApk(outputList); + writeCEH(outputList); + buffer.close(); + } + private void writeCEH(List outputList) throws IOException{ + EndRecord endRecord = new EndRecord(); + endRecord.setSignature(ZipSignature.END_RECORD); + long offset = position(); + endRecord.setOffsetOfCentralDirectory((int) offset); + endRecord.setNumberOfDirectories(outputList.size()); + endRecord.setTotalNumberOfDirectories(outputList.size()); + for(OutputSource outputSource:outputList){ + outputSource.writeCEH(this); + } + long len = position() - offset; + endRecord.setLengthOfCentralDirectory(len); + endRecord.writeBytes(getOutputStream()); + } + private void writeApk(List outputList) throws IOException{ + for(OutputSource outputSource:outputList){ + outputSource.writeApk( this); + } + } + private BufferFileInput writeBuffer(List outputList) throws IOException { + File bufferFile = getBufferFile(); + BufferFileOutput output = new BufferFileOutput(bufferFile); + BufferFileInput input = new BufferFileInput(bufferFile); + for(OutputSource outputSource:outputList){ + outputSource.makeBuffer(input, output); + } + output.close(); + return input; + } + private void align(ZipAligner aligner, List outputList) throws IOException{ + for(OutputSource outputSource:outputList){ + outputSource.align(aligner); + } + } + private File getBufferFile(){ + File file = getFile(); + File dir = file.getParentFile(); + String name = file.getAbsolutePath(); + name = "tmp" + name.hashCode(); + File bufFile; + if(dir != null){ + bufFile = new File(dir, name); + }else { + bufFile = new File(name); + } + bufFile.deleteOnExit(); + return bufFile; + } + private List buildOutputEntry(){ + Collection sourceList = this.sourceList; + List results = new ArrayList<>(sourceList.size()); + for(InputSource inputSource:sourceList){ + results.add(toOutputSource(inputSource)); + } + return results; + } + private OutputSource toOutputSource(InputSource inputSource){ + if(inputSource instanceof ArchiveEntrySource){ + return new ArchiveOutputSource(inputSource); + } + if(inputSource instanceof RenamedInputSource){ + InputSource renamed = ((RenamedInputSource) inputSource).getInputSource(); + if(renamed instanceof ArchiveEntrySource){ + return new RenamedArchiveSource((RenamedInputSource) inputSource); + } + } + return new OutputSource(inputSource); + } + +} diff --git a/src/main/java/com/reandroid/archive2/writter/ArchiveOutputSource.java b/src/main/java/com/reandroid/archive2/writter/ArchiveOutputSource.java new file mode 100644 index 0000000..53385f7 --- /dev/null +++ b/src/main/java/com/reandroid/archive2/writter/ArchiveOutputSource.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2022 github.com/REAndroid + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.reandroid.archive2.writter; + +import com.reandroid.archive.InputSource; +import com.reandroid.archive2.block.LocalFileHeader; +import com.reandroid.archive2.io.ArchiveEntrySource; +import com.reandroid.archive2.io.ZipFileInput; +import com.reandroid.archive2.io.ZipInput; + + +public class ArchiveOutputSource extends OutputSource{ + public ArchiveOutputSource(InputSource inputSource){ + super(inputSource); + } + + ArchiveEntrySource getArchiveSource(){ + return (ArchiveEntrySource) super.getInputSource(); + } + @Override + EntryBuffer makeFromEntry(){ + ArchiveEntrySource entrySource = getArchiveSource(); + ZipInput zip = entrySource.getZipSource(); + if(!(zip instanceof ZipFileInput)){ + return null; + } + LocalFileHeader lfh = entrySource.getArchiveEntry().getLocalFileHeader(); + if(lfh.getMethod() != getInputSource().getMethod()){ + return null; + } + return new EntryBuffer((ZipFileInput) zip, + lfh.getFileOffset(), + lfh.getDataSize()); + } + @Override + public LocalFileHeader createLocalFileHeader(){ + ArchiveEntrySource source = getArchiveSource(); + return source.getArchiveEntry().getLocalFileHeader(); + } +} diff --git a/src/main/java/com/reandroid/archive2/writter/BufferFileInput.java b/src/main/java/com/reandroid/archive2/writter/BufferFileInput.java new file mode 100644 index 0000000..d4aa629 --- /dev/null +++ b/src/main/java/com/reandroid/archive2/writter/BufferFileInput.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 github.com/REAndroid + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.reandroid.archive2.writter; + +import com.reandroid.archive2.io.ZipFileInput; +import com.reandroid.archive2.io.ZipInput; + +import java.io.File; +import java.io.IOException; +import java.nio.channels.FileChannel; + +public class BufferFileInput extends ZipFileInput { + private boolean unlocked; + public BufferFileInput(File file){ + super(file); + } + + public void unlock(){ + this.unlocked = true; + } + + @Override + public FileChannel getFileChannel() throws IOException { + if(unlocked){ + return super.getFileChannel(); + } + throw new IOException("File locked!"); + } +} diff --git a/src/main/java/com/reandroid/archive2/writter/BufferFileOutput.java b/src/main/java/com/reandroid/archive2/writter/BufferFileOutput.java new file mode 100644 index 0000000..23e2960 --- /dev/null +++ b/src/main/java/com/reandroid/archive2/writter/BufferFileOutput.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2022 github.com/REAndroid + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.reandroid.archive2.writter; + +import com.reandroid.archive2.io.ZipFileOutput; + +import java.io.File; +import java.io.IOException; + +public class BufferFileOutput extends ZipFileOutput { + public BufferFileOutput(File file) throws IOException { + super(file); + } +} diff --git a/src/main/java/com/reandroid/archive2/writter/EntryBuffer.java b/src/main/java/com/reandroid/archive2/writter/EntryBuffer.java new file mode 100644 index 0000000..f4ae904 --- /dev/null +++ b/src/main/java/com/reandroid/archive2/writter/EntryBuffer.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2022 github.com/REAndroid + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.reandroid.archive2.writter; + +import com.reandroid.archive2.io.ZipFileInput; + +public class EntryBuffer { + private final ZipFileInput zipFileInput; + private final long offset; + private final long length; + public EntryBuffer(ZipFileInput zipFileInput, long offset, long length){ + this.zipFileInput = zipFileInput; + this.offset = offset; + this.length = length; + } + + public ZipFileInput getZipFileInput() { + return zipFileInput; + } + public long getOffset() { + return offset; + } + public long getLength() { + return length; + } + +} diff --git a/src/main/java/com/reandroid/archive2/writter/OutputSource.java b/src/main/java/com/reandroid/archive2/writter/OutputSource.java new file mode 100644 index 0000000..2e6e9cb --- /dev/null +++ b/src/main/java/com/reandroid/archive2/writter/OutputSource.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2022 github.com/REAndroid + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.reandroid.archive2.writter; + +import com.reandroid.archive.InputSource; +import com.reandroid.archive2.ZipSignature; +import com.reandroid.archive2.block.CentralEntryHeader; +import com.reandroid.archive2.block.DataDescriptor; +import com.reandroid.archive2.block.LocalFileHeader; +import com.reandroid.archive2.io.CountingOutputStream; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.channels.FileChannel; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.ZipEntry; + +public class OutputSource { + private final InputSource inputSource; + private LocalFileHeader lfh; + private EntryBuffer entryBuffer; + + public OutputSource(InputSource inputSource){ + this.inputSource = inputSource; + } + public void align(ZipAligner aligner){ + aligner.align(getInputSource(), getLocalFileHeader()); + } + public void makeBuffer(BufferFileInput input, BufferFileOutput output) throws IOException { + EntryBuffer entryBuffer = this.entryBuffer; + if(entryBuffer != null){ + return; + } + entryBuffer = makeFromEntry(); + if(entryBuffer != null){ + this.entryBuffer = entryBuffer; + return; + } + this.entryBuffer = writeBuffer(input, output); + } + private EntryBuffer writeBuffer(BufferFileInput input, BufferFileOutput output) throws IOException { + long offset = output.position(); + onWriteBuffer(output); + long length = output.position() - offset; + return new EntryBuffer(input, offset, length); + } + EntryBuffer makeFromEntry(){ + return null; + } + + public void writeApk(ApkWriter apkWriter) throws IOException{ + EntryBuffer entryBuffer = this.entryBuffer; + FileChannel input = entryBuffer.getZipFileInput().getFileChannel(); + input.position(entryBuffer.getOffset()); + LocalFileHeader lfh = getLocalFileHeader(); + writeLFH(lfh, apkWriter); + writeData(input, entryBuffer.getLength(), apkWriter); + writeDD(lfh.getDataDescriptor(), apkWriter); + } + public void writeCEH(ApkWriter apkWriter) throws IOException{ + LocalFileHeader lfh = getLocalFileHeader(); + CentralEntryHeader ceh = CentralEntryHeader.fromLocalFileHeader(lfh); + ceh.writeBytes(apkWriter.getOutputStream()); + } + private void writeLFH(LocalFileHeader lfh, ApkWriter apkWriter) throws IOException{ + lfh.writeBytes(apkWriter.getOutputStream()); + } + private void writeData(FileChannel input, long length, ApkWriter apkWriter) throws IOException{ + long offset = apkWriter.position(); + LocalFileHeader lfh = getLocalFileHeader(); + lfh.setFileOffset(offset); + apkWriter.write(input, length); + } + void writeDD(DataDescriptor dataDescriptor, ApkWriter apkWriter) throws IOException{ + if(dataDescriptor == null){ + return; + } + dataDescriptor.writeBytes(apkWriter.getOutputStream()); + } + void onWriteBuffer(BufferFileOutput output) throws IOException { + LocalFileHeader lfh = getLocalFileHeader(); + + InputSource inputSource = getInputSource(); + OutputStream rawStream = output.getOutputStream(); + + CountingOutputStream rawCounter = new CountingOutputStream<>(rawStream); + CountingOutputStream deflateCounter = null; + + if(inputSource.getMethod() != ZipEntry.STORED){ + DeflaterOutputStream deflaterInputStream = + new DeflaterOutputStream(rawCounter, new Deflater(Deflater.BEST_SPEED, true), true); + deflateCounter = new CountingOutputStream<>(deflaterInputStream, false); + } + if(deflateCounter != null){ + rawCounter.disableCrc(true); + inputSource.write(deflateCounter); + deflateCounter.close(); + rawCounter.close(); + }else { + inputSource.write(rawCounter); + } + + lfh.setCompressedSize(rawCounter.getSize()); + + if(deflateCounter != null){ + lfh.setMethod(ZipEntry.DEFLATED); + lfh.setCrc(deflateCounter.getCrc()); + lfh.setSize(deflateCounter.getSize()); + }else { + lfh.setSize(rawCounter.getSize()); + lfh.setMethod(ZipEntry.STORED); + lfh.setCrc(rawCounter.getCrc()); + } + } + + public InputSource getInputSource() { + return inputSource; + } + public LocalFileHeader getLocalFileHeader(){ + if(lfh == null){ + lfh = createLocalFileHeader(); + clearAlignment(lfh); + } + return lfh; + } + LocalFileHeader createLocalFileHeader(){ + InputSource inputSource = getInputSource(); + LocalFileHeader lfh = new LocalFileHeader(); + lfh.setSignature(ZipSignature.LOCAL_FILE); + lfh.getGeneralPurposeFlag().initDefault(); + lfh.setFileName(inputSource.getAlias()); + lfh.setMethod(inputSource.getMethod()); + return lfh; + } + private void clearAlignment(LocalFileHeader lfh){ + lfh.getGeneralPurposeFlag().setHasDataDescriptor(false); + lfh.setDataDescriptor(null); + lfh.setExtra(null); + } +} diff --git a/src/main/java/com/reandroid/archive2/writter/RenamedArchiveSource.java b/src/main/java/com/reandroid/archive2/writter/RenamedArchiveSource.java new file mode 100644 index 0000000..4e9ab7d --- /dev/null +++ b/src/main/java/com/reandroid/archive2/writter/RenamedArchiveSource.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 github.com/REAndroid + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.reandroid.archive2.writter; + +import com.reandroid.apk.RenamedInputSource; +import com.reandroid.archive2.io.ArchiveEntrySource; + +public class RenamedArchiveSource extends ArchiveOutputSource{ + public RenamedArchiveSource(RenamedInputSource inputSource) { + super(inputSource); + } + @Override + ArchiveEntrySource getArchiveSource(){ + return (ArchiveEntrySource) + ((RenamedInputSource)super.getInputSource()).getInputSource(); + } +} diff --git a/src/main/java/com/reandroid/archive2/writter/ZipAligner.java b/src/main/java/com/reandroid/archive2/writter/ZipAligner.java new file mode 100644 index 0000000..3eba785 --- /dev/null +++ b/src/main/java/com/reandroid/archive2/writter/ZipAligner.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2022 github.com/REAndroid + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.reandroid.archive2.writter; + +import com.reandroid.archive.InputSource; +import com.reandroid.archive2.block.DataDescriptor; +import com.reandroid.archive2.block.LocalFileHeader; + +import java.util.zip.ZipEntry; + +public class ZipAligner { + private long mTotalPadding; + private long mCurrentOffset; + private boolean enableDataDescriptor = true; + public ZipAligner(){ + } + public void align(InputSource inputSource, LocalFileHeader lfh){ + int padding; + if(inputSource.getMethod() != ZipEntry.STORED){ + padding = 0; + createDataDescriptor(lfh); + }else { + int alignment = getAlignment(inputSource); + long newOffset = mCurrentOffset + mTotalPadding; + padding = (int) ((alignment - (newOffset % alignment)) % alignment); + mTotalPadding += padding; + } + lfh.setExtra(new byte[padding]); + mCurrentOffset += lfh.getDataSize() + lfh.countBytes(); + DataDescriptor dataDescriptor = lfh.getDataDescriptor(); + if(dataDescriptor!=null){ + mCurrentOffset += dataDescriptor.countBytes(); + } + } + private void createDataDescriptor(LocalFileHeader lfh){ + DataDescriptor dataDescriptor; + if(enableDataDescriptor){ + dataDescriptor = DataDescriptor.fromLocalFile(lfh); + }else { + dataDescriptor = null; + } + lfh.setDataDescriptor(dataDescriptor); + } + public void reset(){ + mTotalPadding = 0; + mCurrentOffset = 0; + } + public void setEnableDataDescriptor(boolean enableDataDescriptor) { + this.enableDataDescriptor = enableDataDescriptor; + } + + private int getAlignment(InputSource inputSource){ + String name = inputSource.getAlias(); + if(name.startsWith("lib/") && name.endsWith(".so")){ + return ALIGNMENT_PAGE; + }else { + return ALIGNMENT_4; + } + } + + private static final int ALIGNMENT_4 = 4; + private static final int ALIGNMENT_PAGE = 4096; +}