mirror of
https://github.com/revanced/ARSCLib.git
synced 2025-04-30 06:14:25 +02:00
implement archive2
This commit is contained in:
parent
eed6c3cd24
commit
effd893de3
@ -16,6 +16,8 @@
|
|||||||
package com.reandroid.apk;
|
package com.reandroid.apk;
|
||||||
|
|
||||||
import com.reandroid.archive.*;
|
import com.reandroid.archive.*;
|
||||||
|
import com.reandroid.archive2.Archive;
|
||||||
|
import com.reandroid.archive2.writter.ApkWriter;
|
||||||
import com.reandroid.arsc.ApkFile;
|
import com.reandroid.arsc.ApkFile;
|
||||||
import com.reandroid.arsc.array.PackageArray;
|
import com.reandroid.arsc.array.PackageArray;
|
||||||
import com.reandroid.arsc.chunk.Chunk;
|
import com.reandroid.arsc.chunk.Chunk;
|
||||||
@ -268,10 +270,15 @@ public class ApkModule implements ApkFile {
|
|||||||
if(manifest!=null){
|
if(manifest!=null){
|
||||||
manifest.setSort(0);
|
manifest.setSort(0);
|
||||||
}
|
}
|
||||||
|
ApkWriter apkWriter = new ApkWriter(file, archive.listInputSources());
|
||||||
|
apkWriter.write();
|
||||||
|
apkWriter.close();
|
||||||
|
/*
|
||||||
ZipSerializer serializer=new ZipSerializer(archive.listInputSources());
|
ZipSerializer serializer=new ZipSerializer(archive.listInputSources());
|
||||||
serializer.setWriteProgress(progress);
|
serializer.setWriteProgress(progress);
|
||||||
serializer.setWriteInterceptor(interceptor);
|
serializer.setWriteInterceptor(interceptor);
|
||||||
serializer.writeZip(file);
|
serializer.writeZip(file);
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
private void uncompressNonXmlResFiles() {
|
private void uncompressNonXmlResFiles() {
|
||||||
for(ResFile resFile:listResFiles()){
|
for(ResFile resFile:listResFiles()){
|
||||||
@ -725,7 +732,7 @@ public class ApkModule implements ApkFile {
|
|||||||
return loadApkFile(apkFile, ApkUtil.DEF_MODULE_NAME);
|
return loadApkFile(apkFile, ApkUtil.DEF_MODULE_NAME);
|
||||||
}
|
}
|
||||||
public static ApkModule loadApkFile(File apkFile, String moduleName) throws IOException {
|
public static ApkModule loadApkFile(File apkFile, String moduleName) throws IOException {
|
||||||
APKArchive archive=APKArchive.loadZippedApk(apkFile);
|
Archive archive = new Archive(apkFile);
|
||||||
return new ApkModule(moduleName, archive);
|
return new ApkModule(moduleName, archive.createAPKArchive());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,7 @@ public abstract class InputSource {
|
|||||||
}
|
}
|
||||||
private long write(OutputStream outputStream, InputStream inputStream) throws IOException {
|
private long write(OutputStream outputStream, InputStream inputStream) throws IOException {
|
||||||
long result=0;
|
long result=0;
|
||||||
byte[] buffer=new byte[10240];
|
byte[] buffer=new byte[1024 * 1000];
|
||||||
int len;
|
int len;
|
||||||
while ((len=inputStream.read(buffer))>0){
|
while ((len=inputStream.read(buffer))>0){
|
||||||
outputStream.write(buffer, 0, len);
|
outputStream.write(buffer, 0, len);
|
||||||
|
@ -15,10 +15,13 @@
|
|||||||
*/
|
*/
|
||||||
package com.reandroid.archive2;
|
package com.reandroid.archive2;
|
||||||
|
|
||||||
|
import com.reandroid.archive.APKArchive;
|
||||||
|
import com.reandroid.archive.InputSource;
|
||||||
import com.reandroid.archive2.block.*;
|
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.ArchiveUtil;
|
||||||
import com.reandroid.archive2.io.ZipSource;
|
import com.reandroid.archive2.io.ZipInput;
|
||||||
import com.reandroid.archive2.model.LocalFileDirectory;
|
import com.reandroid.archive2.model.LocalFileDirectory;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@ -26,23 +29,25 @@ import java.io.FileOutputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.zip.Inflater;
|
import java.util.zip.Inflater;
|
||||||
import java.util.zip.InflaterInputStream;
|
import java.util.zip.InflaterInputStream;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
|
|
||||||
public class Archive {
|
public class Archive {
|
||||||
private final ZipSource zipSource;
|
private final ZipInput zipInput;
|
||||||
private final List<ArchiveEntry> entryList;
|
private final List<ArchiveEntry> entryList;
|
||||||
private final EndRecord endRecord;
|
private final EndRecord endRecord;
|
||||||
private final ApkSignatureBlock apkSignatureBlock;
|
private final ApkSignatureBlock apkSignatureBlock;
|
||||||
public Archive(ZipSource zipSource) throws IOException {
|
public Archive(ZipInput zipInput) throws IOException {
|
||||||
this.zipSource = zipSource;
|
this.zipInput = zipInput;
|
||||||
LocalFileDirectory lfd = new LocalFileDirectory();
|
LocalFileDirectory lfd = new LocalFileDirectory();
|
||||||
lfd.visit(zipSource);
|
lfd.visit(zipInput);
|
||||||
List<LocalFileHeader> localFileHeaderList = lfd.getHeaderList();
|
List<LocalFileHeader> localFileHeaderList = lfd.getHeaderList();
|
||||||
List<CentralEntryHeader> centralEntryHeaderList = lfd.getCentralFileDirectory().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++){
|
for(int i=0;i<localFileHeaderList.size();i++){
|
||||||
LocalFileHeader lfh = localFileHeaderList.get(i);
|
LocalFileHeader lfh = localFileHeaderList.get(i);
|
||||||
CentralEntryHeader ceh = centralEntryHeaderList.get(i);
|
CentralEntryHeader ceh = centralEntryHeaderList.get(i);
|
||||||
@ -54,10 +59,24 @@ public class Archive {
|
|||||||
this.apkSignatureBlock = lfd.getApkSigBlock();
|
this.apkSignatureBlock = lfd.getApkSigBlock();
|
||||||
}
|
}
|
||||||
public Archive(File file) throws IOException {
|
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 {
|
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 {
|
public InputStream openInputStream(ArchiveEntry archiveEntry) throws IOException {
|
||||||
InputStream rawInputStream = openRawInputStream(archiveEntry);
|
InputStream rawInputStream = openRawInputStream(archiveEntry);
|
||||||
|
@ -53,7 +53,7 @@ public class ArchiveEntry extends ZipEntry {
|
|||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public long getSize() {
|
public long getSize() {
|
||||||
return localFileHeader.getSize();
|
return centralEntryHeader.getSize();
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void setSize(long size) {
|
public void setSize(long size) {
|
||||||
@ -62,7 +62,7 @@ public class ArchiveEntry extends ZipEntry {
|
|||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public long getCrc() {
|
public long getCrc() {
|
||||||
return localFileHeader.getCrc();
|
return centralEntryHeader.getCrc();
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void setCrc(long crc) {
|
public void setCrc(long crc) {
|
||||||
@ -71,7 +71,7 @@ public class ArchiveEntry extends ZipEntry {
|
|||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public long getCompressedSize() {
|
public long getCompressedSize() {
|
||||||
return localFileHeader.getCompressedSize();
|
return centralEntryHeader.getCompressedSize();
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void setCompressedSize(long csize) {
|
public void setCompressedSize(long csize) {
|
||||||
@ -83,7 +83,7 @@ public class ArchiveEntry extends ZipEntry {
|
|||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public String getName(){
|
public String getName(){
|
||||||
return localFileHeader.getFileName();
|
return centralEntryHeader.getFileName();
|
||||||
}
|
}
|
||||||
public void setName(String name){
|
public void setName(String name){
|
||||||
centralEntryHeader.setFileName(name);
|
centralEntryHeader.setFileName(name);
|
||||||
|
@ -87,6 +87,12 @@ public class CentralEntryHeader extends CommonHeader {
|
|||||||
setBytesLength(length, false);
|
setBytesLength(length, false);
|
||||||
putShort(OFFSET_commentLength, value);
|
putShort(OFFSET_commentLength, value);
|
||||||
}
|
}
|
||||||
|
public long getLocalRelativeOffset(){
|
||||||
|
return getIntegerUnsigned(OFFSET_localRelativeOffset);
|
||||||
|
}
|
||||||
|
public void setLocalRelativeOffset(long offset){
|
||||||
|
putInteger(OFFSET_localRelativeOffset, offset);
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
void onUtf8Changed(boolean oldValue){
|
void onUtf8Changed(boolean oldValue){
|
||||||
String str = mComment;
|
String str = mComment;
|
||||||
@ -139,6 +145,7 @@ public class CentralEntryHeader extends CommonHeader {
|
|||||||
builder.append(", fileNameLength=").append(getFileNameLength());
|
builder.append(", fileNameLength=").append(getFileNameLength());
|
||||||
builder.append(", extraLength=").append(getExtraLength());
|
builder.append(", extraLength=").append(getExtraLength());
|
||||||
builder.append(", commentLength=").append(getCommentLength());
|
builder.append(", commentLength=").append(getCommentLength());
|
||||||
|
builder.append(", offset=").append(getLocalRelativeOffset());
|
||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,7 +153,9 @@ public class CentralEntryHeader extends CommonHeader {
|
|||||||
public static CentralEntryHeader fromLocalFileHeader(LocalFileHeader lfh){
|
public static CentralEntryHeader fromLocalFileHeader(LocalFileHeader lfh){
|
||||||
CentralEntryHeader ceh = new CentralEntryHeader();
|
CentralEntryHeader ceh = new CentralEntryHeader();
|
||||||
ceh.setSignature(ZipSignature.CENTRAL_FILE);
|
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.getGeneralPurposeFlag().setValue(lfh.getGeneralPurposeFlag().getValue());
|
||||||
ceh.setMethod(lfh.getMethod());
|
ceh.setMethod(lfh.getMethod());
|
||||||
ceh.setDosTime(lfh.getDosTime());
|
ceh.setDosTime(lfh.getDosTime());
|
||||||
|
@ -49,6 +49,12 @@ public abstract class CommonHeader extends ZipHeader {
|
|||||||
}
|
}
|
||||||
return getCompressedSize();
|
return getCompressedSize();
|
||||||
}
|
}
|
||||||
|
public void setDataSize(long size){
|
||||||
|
if(getMethod() == ZipEntry.STORED){
|
||||||
|
setSize(size);
|
||||||
|
}
|
||||||
|
setCompressedSize(size);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
int readNext(InputStream inputStream) throws IOException {
|
int readNext(InputStream inputStream) throws IOException {
|
||||||
@ -113,6 +119,8 @@ public abstract class CommonHeader extends ZipHeader {
|
|||||||
}
|
}
|
||||||
public void setMethod(int value){
|
public void setMethod(int value){
|
||||||
putShort(offsetGeneralPurpose + 2, value);
|
putShort(offsetGeneralPurpose + 2, value);
|
||||||
|
GeneralPurposeFlag gpf = getGeneralPurposeFlag();
|
||||||
|
//gpf.setHasDataDescriptor(value != ZipEntry.STORED);
|
||||||
}
|
}
|
||||||
public long getDosTime(){
|
public long getDosTime(){
|
||||||
return getIntegerUnsigned(offsetGeneralPurpose + 4);
|
return getIntegerUnsigned(offsetGeneralPurpose + 4);
|
||||||
@ -353,27 +361,35 @@ public abstract class CommonHeader extends ZipHeader {
|
|||||||
return this.localFileHeader.getBit(offset + 1, 3);
|
return this.localFileHeader.getBit(offset + 1, 3);
|
||||||
}
|
}
|
||||||
public void setUtf8(boolean flag){
|
public void setUtf8(boolean flag){
|
||||||
|
setUtf8(flag, true);
|
||||||
|
}
|
||||||
|
private void setUtf8(boolean flag, boolean notify){
|
||||||
boolean oldUtf8 = getUtf8();
|
boolean oldUtf8 = getUtf8();
|
||||||
if(oldUtf8 == flag){
|
if(oldUtf8 == flag){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.localFileHeader.putBit(offset +1, 3, flag);
|
this.localFileHeader.putBit(offset +1, 3, flag);
|
||||||
|
if(notify){
|
||||||
this.localFileHeader.onUtf8Changed(oldUtf8);
|
this.localFileHeader.onUtf8Changed(oldUtf8);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public int getValue(){
|
public int getValue(){
|
||||||
return this.localFileHeader.getInteger(offset);
|
return this.localFileHeader.getShortUnsigned(offset);
|
||||||
}
|
}
|
||||||
public void setValue(int value){
|
public void setValue(int value){
|
||||||
if(value == getValue()){
|
if(value == getValue()){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
boolean oldUtf8 = getUtf8();
|
boolean oldUtf8 = getUtf8();
|
||||||
this.localFileHeader.putInteger(offset, value);
|
this.localFileHeader.putShort(offset, value);
|
||||||
if(oldUtf8 != getUtf8()){
|
if(oldUtf8 != getUtf8()){
|
||||||
this.localFileHeader.onUtf8Changed(oldUtf8);
|
this.localFileHeader.onUtf8Changed(oldUtf8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public void initDefault(){
|
||||||
|
setUtf8(false, false);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString(){
|
public String toString(){
|
||||||
|
@ -49,6 +49,14 @@ public class DataDescriptor extends ZipHeader{
|
|||||||
builder.append(", size=").append(getSize());
|
builder.append(", size=").append(getSize());
|
||||||
return builder.toString();
|
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_crc = 4;
|
||||||
private static final int OFFSET_compressed_size = 8;
|
private static final int OFFSET_compressed_size = 8;
|
||||||
|
@ -87,9 +87,6 @@ public abstract class LengthPrefixedList<T extends Block> extends FixedBlockCont
|
|||||||
}
|
}
|
||||||
BlockReader chunkReader = reader.create(totalSize);
|
BlockReader chunkReader = reader.create(totalSize);
|
||||||
readElements(chunkReader);
|
readElements(chunkReader);
|
||||||
if(chunkReader.isAvailable()){
|
|
||||||
String junk = "";
|
|
||||||
}
|
|
||||||
bottomContainer.readBytes(chunkReader);
|
bottomContainer.readBytes(chunkReader);
|
||||||
reader.offset(totalSize);
|
reader.offset(totalSize);
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,6 @@ package com.reandroid.archive2.block;
|
|||||||
|
|
||||||
import com.reandroid.archive2.ZipSignature;
|
import com.reandroid.archive2.ZipSignature;
|
||||||
|
|
||||||
|
|
||||||
public class LocalFileHeader extends CommonHeader {
|
public class LocalFileHeader extends CommonHeader {
|
||||||
private DataDescriptor dataDescriptor;
|
private DataDescriptor dataDescriptor;
|
||||||
public LocalFileHeader(){
|
public LocalFileHeader(){
|
||||||
@ -48,6 +47,7 @@ public class LocalFileHeader extends CommonHeader {
|
|||||||
}
|
}
|
||||||
public void setDataDescriptor(DataDescriptor dataDescriptor){
|
public void setDataDescriptor(DataDescriptor dataDescriptor){
|
||||||
this.dataDescriptor = dataDescriptor;
|
this.dataDescriptor = dataDescriptor;
|
||||||
|
getGeneralPurposeFlag().setHasDataDescriptor(dataDescriptor!=null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LocalFileHeader fromCentralEntryHeader(CentralEntryHeader ceh){
|
public static LocalFileHeader fromCentralEntryHeader(CentralEntryHeader ceh){
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
129
src/main/java/com/reandroid/archive2/io/CountingInputStream.java
Normal file
129
src/main/java/com/reandroid/archive2/io/CountingInputStream.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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?");
|
||||||
|
}
|
||||||
|
}
|
@ -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(){
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
30
src/main/java/com/reandroid/archive2/io/RandomStream.java
Normal file
30
src/main/java/com/reandroid/archive2/io/RandomStream.java
Normal 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;
|
||||||
|
}
|
24
src/main/java/com/reandroid/archive2/io/ReadOnlyStream.java
Normal file
24
src/main/java/com/reandroid/archive2/io/ReadOnlyStream.java
Normal 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;
|
||||||
|
}
|
26
src/main/java/com/reandroid/archive2/io/WriteOnlyStream.java
Normal file
26
src/main/java/com/reandroid/archive2/io/WriteOnlyStream.java
Normal 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;
|
||||||
|
}
|
@ -20,17 +20,39 @@ import java.nio.ByteBuffer;
|
|||||||
import java.nio.channels.FileChannel;
|
import java.nio.channels.FileChannel;
|
||||||
import java.nio.file.StandardOpenOption;
|
import java.nio.file.StandardOpenOption;
|
||||||
|
|
||||||
public class ArchiveFile extends ZipSource{
|
public class ZipFileInput extends ZipInput {
|
||||||
private final File file;
|
private final File file;
|
||||||
private FileChannel fileChannel;
|
private FileChannel fileChannel;
|
||||||
private SlicedInputStream mCurrentInputStream;
|
private InputStream mCurrentInputStream;
|
||||||
public ArchiveFile(File file){
|
public ZipFileInput(File file){
|
||||||
this.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
|
@Override
|
||||||
public long getLength(){
|
public long getLength(){
|
||||||
return this.file.length();
|
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
|
@Override
|
||||||
public byte[] getFooter(int minLength) throws IOException {
|
public byte[] getFooter(int minLength) throws IOException {
|
||||||
long position = getLength();
|
long position = getLength();
|
||||||
@ -45,16 +67,7 @@ public class ArchiveFile extends ZipSource{
|
|||||||
return buffer.array();
|
return buffer.array();
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public InputStream getInputStream(long offset, long length) throws IOException {
|
public FileChannel getFileChannel() 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 {
|
|
||||||
FileChannel fileChannel = this.fileChannel;
|
FileChannel fileChannel = this.fileChannel;
|
||||||
if(fileChannel != null){
|
if(fileChannel != null){
|
||||||
return fileChannel;
|
return fileChannel;
|
||||||
@ -67,8 +80,18 @@ public class ArchiveFile extends ZipSource{
|
|||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
closeChannel();
|
|
||||||
closeCurrentInputStream();
|
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 {
|
private void closeChannel() throws IOException {
|
||||||
FileChannel fileChannel = this.fileChannel;
|
FileChannel fileChannel = this.fileChannel;
|
||||||
@ -81,7 +104,7 @@ public class ArchiveFile extends ZipSource{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
private void closeCurrentInputStream() throws IOException {
|
private void closeCurrentInputStream() throws IOException {
|
||||||
SlicedInputStream current = this.mCurrentInputStream;
|
InputStream current = this.mCurrentInputStream;
|
||||||
if(current == null){
|
if(current == null){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
127
src/main/java/com/reandroid/archive2/io/ZipFileOutput.java
Normal file
127
src/main/java/com/reandroid/archive2/io/ZipFileOutput.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
@ -15,14 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.reandroid.archive2.io;
|
package com.reandroid.archive2.io;
|
||||||
|
|
||||||
import java.io.Closeable;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
public abstract class ZipSource implements Closeable {
|
public abstract class ZipInput implements ReadOnlyStream {
|
||||||
public abstract long getLength();
|
|
||||||
public abstract byte[] getFooter(int minLength) throws IOException;
|
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;
|
|
||||||
}
|
}
|
20
src/main/java/com/reandroid/archive2/io/ZipOutput.java
Normal file
20
src/main/java/com/reandroid/archive2/io/ZipOutput.java
Normal 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{
|
||||||
|
|
||||||
|
}
|
@ -19,7 +19,7 @@ import com.reandroid.archive2.block.CentralEntryHeader;
|
|||||||
import com.reandroid.archive2.block.EndRecord;
|
import com.reandroid.archive2.block.EndRecord;
|
||||||
import com.reandroid.archive2.block.LocalFileHeader;
|
import com.reandroid.archive2.block.LocalFileHeader;
|
||||||
import com.reandroid.archive2.block.SignatureFooter;
|
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.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -73,13 +73,13 @@ public class CentralFileDirectory {
|
|||||||
public EndRecord getEndRecord() {
|
public EndRecord getEndRecord() {
|
||||||
return endRecord;
|
return endRecord;
|
||||||
}
|
}
|
||||||
public void visit(ZipSource zipSource) throws IOException {
|
public void visit(ZipInput zipInput) throws IOException {
|
||||||
byte[] footer = zipSource.getFooter(SignatureFooter.MIN_SIZE + EndRecord.MAX_LENGTH);
|
byte[] footer = zipInput.getFooter(SignatureFooter.MIN_SIZE + EndRecord.MAX_LENGTH);
|
||||||
EndRecord endRecord = findEndRecord(footer);
|
EndRecord endRecord = findEndRecord(footer);
|
||||||
int length = (int) endRecord.getLengthOfCentralDirectory();
|
int length = (int) endRecord.getLengthOfCentralDirectory();
|
||||||
int endLength = endRecord.countBytes();
|
int endLength = endRecord.countBytes();
|
||||||
if(footer.length < (length + endLength)){
|
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;
|
int offset = footer.length - length - endLength;
|
||||||
this.endRecord = endRecord;
|
this.endRecord = endRecord;
|
||||||
|
@ -17,7 +17,7 @@ package com.reandroid.archive2.model;
|
|||||||
|
|
||||||
import com.reandroid.archive2.block.*;
|
import com.reandroid.archive2.block.*;
|
||||||
import com.reandroid.archive2.block.ApkSignatureBlock;
|
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 com.reandroid.arsc.io.BlockReader;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -37,14 +37,14 @@ public class LocalFileDirectory {
|
|||||||
public LocalFileDirectory(){
|
public LocalFileDirectory(){
|
||||||
this(new CentralFileDirectory());
|
this(new CentralFileDirectory());
|
||||||
}
|
}
|
||||||
public void visit(ZipSource zipSource) throws IOException {
|
public void visit(ZipInput zipInput) throws IOException {
|
||||||
getCentralFileDirectory().visit(zipSource);
|
getCentralFileDirectory().visit(zipInput);
|
||||||
visitLocalFile(zipSource);
|
visitLocalFile(zipInput);
|
||||||
visitApkSigBlock(zipSource);
|
visitApkSigBlock(zipInput);
|
||||||
}
|
}
|
||||||
private void visitLocalFile(ZipSource zipSource) throws IOException {
|
private void visitLocalFile(ZipInput zipInput) throws IOException {
|
||||||
EndRecord endRecord = getCentralFileDirectory().getEndRecord();
|
EndRecord endRecord = getCentralFileDirectory().getEndRecord();
|
||||||
InputStream inputStream = zipSource.getInputStream(0, endRecord.getOffsetOfCentralDirectory());
|
InputStream inputStream = zipInput.getInputStream(0, endRecord.getOffsetOfCentralDirectory());
|
||||||
visitLocalFile(inputStream);
|
visitLocalFile(inputStream);
|
||||||
inputStream.close();
|
inputStream.close();
|
||||||
}
|
}
|
||||||
@ -81,7 +81,7 @@ public class LocalFileDirectory {
|
|||||||
}
|
}
|
||||||
mTotalDataLength = offset;
|
mTotalDataLength = offset;
|
||||||
}
|
}
|
||||||
private void visitApkSigBlock(ZipSource zipSource) throws IOException{
|
private void visitApkSigBlock(ZipInput zipInput) throws IOException{
|
||||||
CentralFileDirectory cfd = getCentralFileDirectory();
|
CentralFileDirectory cfd = getCentralFileDirectory();
|
||||||
SignatureFooter footer = cfd.getSignatureFooter();
|
SignatureFooter footer = cfd.getSignatureFooter();
|
||||||
if(footer == null || !footer.isValid()){
|
if(footer == null || !footer.isValid()){
|
||||||
@ -91,7 +91,7 @@ public class LocalFileDirectory {
|
|||||||
long length = footer.getSignatureSize() + 8;
|
long length = footer.getSignatureSize() + 8;
|
||||||
long offset = endRecord.getOffsetOfCentralDirectory() - length;
|
long offset = endRecord.getOffsetOfCentralDirectory() - length;
|
||||||
ApkSignatureBlock apkSignatureBlock = new ApkSignatureBlock(footer);
|
ApkSignatureBlock apkSignatureBlock = new ApkSignatureBlock(footer);
|
||||||
apkSignatureBlock.readBytes(new BlockReader(zipSource.getInputStream(offset, length)));
|
apkSignatureBlock.readBytes(new BlockReader(zipInput.getInputStream(offset, length)));
|
||||||
this.apkSignatureBlock = apkSignatureBlock;
|
this.apkSignatureBlock = apkSignatureBlock;
|
||||||
}
|
}
|
||||||
public ApkSignatureBlock getApkSigBlock() {
|
public ApkSignatureBlock getApkSigBlock() {
|
||||||
|
116
src/main/java/com/reandroid/archive2/writter/ApkWriter.java
Normal file
116
src/main/java/com/reandroid/archive2/writter/ApkWriter.java
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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!");
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
154
src/main/java/com/reandroid/archive2/writter/OutputSource.java
Normal file
154
src/main/java/com/reandroid/archive2/writter/OutputSource.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
76
src/main/java/com/reandroid/archive2/writter/ZipAligner.java
Normal file
76
src/main/java/com/reandroid/archive2/writter/ZipAligner.java
Normal 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;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user