implement archive2

This commit is contained in:
REAndroid 2023-04-24 19:35:31 +02:00
parent eed6c3cd24
commit effd893de3
31 changed files with 1340 additions and 59 deletions

View File

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

View File

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

View File

@ -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<ArchiveEntry> 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<LocalFileHeader> localFileHeaderList = lfd.getHeaderList();
List<CentralEntryHeader> centralEntryHeaderList = lfd.getCentralFileDirectory().getHeaderList();
List<ArchiveEntry> entryList = new ArrayList<>();
List<ArchiveEntry> entryList = new ArrayList<>(localFileHeaderList.size());
for(int i=0;i<localFileHeaderList.size();i++){
LocalFileHeader lfh = localFileHeaderList.get(i);
CentralEntryHeader ceh = centralEntryHeaderList.get(i);
@ -54,10 +59,24 @@ public class Archive {
this.apkSignatureBlock = lfd.getApkSigBlock();
}
public Archive(File file) throws IOException {
this(new ArchiveFile(file));
this(new ZipFileInput(file));
}
public APKArchive createAPKArchive(){
return new APKArchive(mapEntrySource());
}
public Map<String, InputSource> mapEntrySource(){
Map<String, InputSource> map = new LinkedHashMap<>();
ZipInput zipInput = this.zipInput;
List<ArchiveEntry> entryList = this.entryList;
for(int i=0; i<entryList.size(); i++){
ArchiveEntry entry = entryList.get(i);
ArchiveEntrySource entrySource = new ArchiveEntrySource(zipInput, entry);
map.put(entrySource.getAlias(), entrySource);
}
return map;
}
public InputStream openRawInputStream(ArchiveEntry archiveEntry) throws IOException {
return zipSource.getInputStream(archiveEntry.getFileOffset(), archiveEntry.getDataSize());
return zipInput.getInputStream(archiveEntry.getFileOffset(), archiveEntry.getDataSize());
}
public InputStream openInputStream(ArchiveEntry archiveEntry) throws IOException {
InputStream rawInputStream = openRawInputStream(archiveEntry);

View File

@ -53,7 +53,7 @@ public class ArchiveEntry extends ZipEntry {
}
@Override
public long getSize() {
return localFileHeader.getSize();
return centralEntryHeader.getSize();
}
@Override
public void setSize(long size) {
@ -62,7 +62,7 @@ public class ArchiveEntry extends ZipEntry {
}
@Override
public long getCrc() {
return localFileHeader.getCrc();
return centralEntryHeader.getCrc();
}
@Override
public void setCrc(long crc) {
@ -71,7 +71,7 @@ public class ArchiveEntry extends ZipEntry {
}
@Override
public long getCompressedSize() {
return localFileHeader.getCompressedSize();
return centralEntryHeader.getCompressedSize();
}
@Override
public void setCompressedSize(long csize) {
@ -83,7 +83,7 @@ public class ArchiveEntry extends ZipEntry {
}
@Override
public String getName(){
return localFileHeader.getFileName();
return centralEntryHeader.getFileName();
}
public void setName(String name){
centralEntryHeader.setFileName(name);

View File

@ -87,6 +87,12 @@ public class CentralEntryHeader extends CommonHeader {
setBytesLength(length, false);
putShort(OFFSET_commentLength, value);
}
public long getLocalRelativeOffset(){
return getIntegerUnsigned(OFFSET_localRelativeOffset);
}
public void setLocalRelativeOffset(long offset){
putInteger(OFFSET_localRelativeOffset, offset);
}
@Override
void onUtf8Changed(boolean oldValue){
String str = mComment;
@ -139,6 +145,7 @@ public class CentralEntryHeader extends CommonHeader {
builder.append(", fileNameLength=").append(getFileNameLength());
builder.append(", extraLength=").append(getExtraLength());
builder.append(", commentLength=").append(getCommentLength());
builder.append(", offset=").append(getLocalRelativeOffset());
return builder.toString();
}
@ -146,7 +153,9 @@ public class CentralEntryHeader extends CommonHeader {
public static CentralEntryHeader fromLocalFileHeader(LocalFileHeader lfh){
CentralEntryHeader ceh = new CentralEntryHeader();
ceh.setSignature(ZipSignature.CENTRAL_FILE);
ceh.setVersionMadeBy(lfh.getVersionMadeBy());
ceh.setVersionMadeBy(0x0300);
long offset = lfh.getFileOffset() - lfh.countBytes();
ceh.setLocalRelativeOffset(offset);
ceh.getGeneralPurposeFlag().setValue(lfh.getGeneralPurposeFlag().getValue());
ceh.setMethod(lfh.getMethod());
ceh.setDosTime(lfh.getDosTime());

View File

@ -49,6 +49,12 @@ public abstract class CommonHeader extends ZipHeader {
}
return getCompressedSize();
}
public void setDataSize(long size){
if(getMethod() == ZipEntry.STORED){
setSize(size);
}
setCompressedSize(size);
}
@Override
int readNext(InputStream inputStream) throws IOException {
@ -113,6 +119,8 @@ public abstract class CommonHeader extends ZipHeader {
}
public void setMethod(int value){
putShort(offsetGeneralPurpose + 2, value);
GeneralPurposeFlag gpf = getGeneralPurposeFlag();
//gpf.setHasDataDescriptor(value != ZipEntry.STORED);
}
public long getDosTime(){
return getIntegerUnsigned(offsetGeneralPurpose + 4);
@ -353,27 +361,35 @@ public abstract class CommonHeader extends ZipHeader {
return this.localFileHeader.getBit(offset + 1, 3);
}
public void setUtf8(boolean flag){
setUtf8(flag, true);
}
private void setUtf8(boolean flag, boolean notify){
boolean oldUtf8 = getUtf8();
if(oldUtf8 == flag){
return;
}
this.localFileHeader.putBit(offset +1, 3, flag);
this.localFileHeader.onUtf8Changed(oldUtf8);
if(notify){
this.localFileHeader.onUtf8Changed(oldUtf8);
}
}
public int getValue(){
return this.localFileHeader.getInteger(offset);
return this.localFileHeader.getShortUnsigned(offset);
}
public void setValue(int value){
if(value == getValue()){
return;
}
boolean oldUtf8 = getUtf8();
this.localFileHeader.putInteger(offset, value);
this.localFileHeader.putShort(offset, value);
if(oldUtf8 != getUtf8()){
this.localFileHeader.onUtf8Changed(oldUtf8);
}
}
public void initDefault(){
setUtf8(false, false);
}
@Override
public String toString(){

View File

@ -49,6 +49,14 @@ public class DataDescriptor extends ZipHeader{
builder.append(", size=").append(getSize());
return builder.toString();
}
public static DataDescriptor fromLocalFile(LocalFileHeader lfh){
DataDescriptor dataDescriptor = new DataDescriptor();
dataDescriptor.setSignature(ZipSignature.DATA_DESCRIPTOR);
dataDescriptor.setSize(lfh.getSize());
dataDescriptor.setCompressedSize(lfh.getCompressedSize());
dataDescriptor.setCrc(lfh.getCrc());
return dataDescriptor;
}
private static final int OFFSET_crc = 4;
private static final int OFFSET_compressed_size = 8;

View File

@ -87,9 +87,6 @@ public abstract class LengthPrefixedList<T extends Block> extends FixedBlockCont
}
BlockReader chunkReader = reader.create(totalSize);
readElements(chunkReader);
if(chunkReader.isAvailable()){
String junk = "";
}
bottomContainer.readBytes(chunkReader);
reader.offset(totalSize);
}

View File

@ -17,7 +17,6 @@ package com.reandroid.archive2.block;
import com.reandroid.archive2.ZipSignature;
public class LocalFileHeader extends CommonHeader {
private DataDescriptor dataDescriptor;
public LocalFileHeader(){
@ -48,6 +47,7 @@ public class LocalFileHeader extends CommonHeader {
}
public void setDataDescriptor(DataDescriptor dataDescriptor){
this.dataDescriptor = dataDescriptor;
getGeneralPurposeFlag().setHasDataDescriptor(dataDescriptor!=null);
}
public static LocalFileHeader fromCentralEntryHeader(CentralEntryHeader ceh){

View File

@ -0,0 +1,71 @@
/*
* 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 com.reandroid.archive.InputSource;
import com.reandroid.archive2.ArchiveEntry;
import com.reandroid.archive2.block.LocalFileHeader;
import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.FileChannel;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
public class ArchiveEntrySource extends InputSource {
private final ZipInput zipInput;
private final ArchiveEntry archiveEntry;
public ArchiveEntrySource(ZipInput zipInput, ArchiveEntry archiveEntry){
super(archiveEntry.getName());
this.zipInput = zipInput;
this.archiveEntry = archiveEntry;
setMethod(archiveEntry.getMethod());
}
public FileChannel getFileChannel() throws IOException {
FileChannel fileChannel = getZipSource().getFileChannel();
fileChannel.position(getFileOffset());
return fileChannel;
}
public ZipInput getZipSource(){
return zipInput;
}
public ArchiveEntry getArchiveEntry() {
return archiveEntry;
}
public long getFileOffset(){
return getArchiveEntry().getFileOffset();
}
@Override
public long getLength() throws IOException{
return getArchiveEntry().getDataSize();
}
@Override
public long getCrc() throws IOException{
return getArchiveEntry().getCrc();
}
@Override
public InputStream openStream() throws IOException {
ArchiveEntry archiveEntry = getArchiveEntry();
LocalFileHeader lfh = archiveEntry.getLocalFileHeader();
InputStream inputStream = getZipSource().getInputStream(
archiveEntry.getFileOffset(), archiveEntry.getDataSize());
if(lfh.getSize() == lfh.getCompressedSize()){
return inputStream;
}
return new InflaterInputStream(inputStream,
new Inflater(true), 512);
}
}

View File

@ -0,0 +1,129 @@
/*
* 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.util.zip.CRC32;
public class CountingInputStream<T extends InputStream> 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();
}
}

View File

@ -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<T extends OutputStream> 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();
}
}

View File

@ -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?");
}
}

View File

@ -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(){
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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() {

View File

@ -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<? extends InputSource> sourceList;
public ApkWriter(File file, Collection<? extends InputSource> sourceList) throws IOException {
super(file);
this.sourceList = sourceList;
}
public void write()throws IOException {
List<OutputSource> 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<OutputSource> 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<OutputSource> outputList) throws IOException{
for(OutputSource outputSource:outputList){
outputSource.writeApk( this);
}
}
private BufferFileInput writeBuffer(List<OutputSource> 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<OutputSource> 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<OutputSource> buildOutputEntry(){
Collection<? extends InputSource> sourceList = this.sourceList;
List<OutputSource> 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);
}
}

View File

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

View File

@ -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!");
}
}

View File

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

View File

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

View File

@ -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<OutputStream> rawCounter = new CountingOutputStream<>(rawStream);
CountingOutputStream<DeflaterOutputStream> 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);
}
}

View File

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

View File

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