[1/2] New zip magic
This commit is contained in:
parent
bc7ee6681d
commit
3cc7496b55
|
|
@ -148,8 +148,8 @@
|
||||||
* <p>Additionally, by adding/removing extensions we can configure what type of apk we want:
|
* <p>Additionally, by adding/removing extensions we can configure what type of apk we want:
|
||||||
*
|
*
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>No SigningExtension ⇒ Aligned, unsigned apk.
|
* <li>No SigningExtension => Aligned, unsigned apk.
|
||||||
* <li>SigningExtension ⇒ Aligned, signed apk.
|
* <li>SigningExtension => Aligned, signed apk.
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* So, by configuring which extensions to add, the package task can decide what type of apk we want.
|
* So, by configuring which extensions to add, the package task can decide what type of apk we want.
|
||||||
|
|
|
||||||
|
|
@ -152,6 +152,25 @@ public class CentralDirectoryHeader implements Cloneable {
|
||||||
file = zFile;
|
file = zFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CentralDirectoryHeader link(String name, byte[] encodedFileName, GPFlags flags) {
|
||||||
|
var newData = new CentralDirectoryHeader(name,
|
||||||
|
encodedFileName,
|
||||||
|
uncompressedSize,
|
||||||
|
compressInfo,
|
||||||
|
flags,
|
||||||
|
file,
|
||||||
|
lastModTime,
|
||||||
|
lastModDate);
|
||||||
|
newData.extraField = extraField;
|
||||||
|
newData.offset = offset;
|
||||||
|
newData.internalAttributes = internalAttributes;
|
||||||
|
newData.externalAttributes = externalAttributes;
|
||||||
|
newData.comment = comment;
|
||||||
|
newData.madeBy = madeBy;
|
||||||
|
newData.crc32 = crc32;
|
||||||
|
return newData;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtains the name of the file.
|
* Obtains the name of the file.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,9 @@ public class ExtraField {
|
||||||
/** Header ID for field with zip alignment. */
|
/** Header ID for field with zip alignment. */
|
||||||
static final int ALIGNMENT_ZIP_EXTRA_DATA_FIELD_HEADER_ID = 0xd935;
|
static final int ALIGNMENT_ZIP_EXTRA_DATA_FIELD_HEADER_ID = 0xd935;
|
||||||
|
|
||||||
|
/** Header ID for field with linking entry. */
|
||||||
|
static final int LINKING_ENTRY_EXTRA_DATA_FIELD_HEADER_ID = 0x2333;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The field's raw data, if it is known. Either this variable or {@link #segments} must be
|
* The field's raw data, if it is known. Either this variable or {@link #segments} must be
|
||||||
* non-{@code null}.
|
* non-{@code null}.
|
||||||
|
|
@ -382,4 +385,43 @@ public class ExtraField {
|
||||||
return ALIGNMENT_ZIP_EXTRA_DATA_FIELD_HEADER_ID;
|
return ALIGNMENT_ZIP_EXTRA_DATA_FIELD_HEADER_ID;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class LinkingEntrySegment implements Segment {
|
||||||
|
|
||||||
|
private final StoredEntry linkingEntry;
|
||||||
|
private int dataOffset = 0;
|
||||||
|
private long zipOffset = 0;
|
||||||
|
|
||||||
|
public LinkingEntrySegment(StoredEntry linkingEntry) throws IOException {
|
||||||
|
Preconditions.checkArgument(linkingEntry.isLinkingEntry(), "linkingEntry is not a linking entry");
|
||||||
|
this.linkingEntry = linkingEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getHeaderId() {
|
||||||
|
return LINKING_ENTRY_EXTRA_DATA_FIELD_HEADER_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return linkingEntry.getLocalHeaderSize() + 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOffset(int dataOffset, long zipOffset) {
|
||||||
|
this.dataOffset = dataOffset;
|
||||||
|
this.zipOffset = zipOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(ByteBuffer out) throws IOException {
|
||||||
|
if (dataOffset == 0 || zipOffset == 0) {
|
||||||
|
throw new IOException("linking entry has 0 offset");
|
||||||
|
}
|
||||||
|
LittleEndianUtils.writeUnsigned2Le(out, LINKING_ENTRY_EXTRA_DATA_FIELD_HEADER_ID);
|
||||||
|
LittleEndianUtils.writeUnsigned2Le(out, linkingEntry.getLocalHeaderSize());
|
||||||
|
var offset = out.position();
|
||||||
|
linkingEntry.writeData(out, dataOffset - linkingEntry.getLocalHeaderSize() - offset);
|
||||||
|
linkingEntry.replaceSourceFromZip(offset + zipOffset);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import com.google.common.base.Preconditions;
|
||||||
import com.google.common.base.Supplier;
|
import com.google.common.base.Supplier;
|
||||||
import com.google.common.base.Suppliers;
|
import com.google.common.base.Suppliers;
|
||||||
import com.google.common.base.Verify;
|
import com.google.common.base.Verify;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.io.ByteStreams;
|
import com.google.common.io.ByteStreams;
|
||||||
import com.google.common.primitives.Ints;
|
import com.google.common.primitives.Ints;
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
|
|
@ -154,6 +155,12 @@ public class StoredEntry {
|
||||||
/** Storage used to create buffers when loading storage into memory. */
|
/** Storage used to create buffers when loading storage into memory. */
|
||||||
private final ByteStorage storage;
|
private final ByteStorage storage;
|
||||||
|
|
||||||
|
/** Entry it is linking to. */
|
||||||
|
private final StoredEntry linkedEntry;
|
||||||
|
|
||||||
|
/** Offset of the nested link */
|
||||||
|
private final int nestedLinkOffset = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new stored entry.
|
* Creates a new stored entry.
|
||||||
*
|
*
|
||||||
|
|
@ -170,11 +177,37 @@ public class StoredEntry {
|
||||||
@Nullable ProcessedAndRawByteSources source,
|
@Nullable ProcessedAndRawByteSources source,
|
||||||
ByteStorage storage)
|
ByteStorage storage)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
this(header, file, source, storage, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
StoredEntry(
|
||||||
|
String name,
|
||||||
|
ZFile file,
|
||||||
|
ByteStorage storage,
|
||||||
|
StoredEntry linkedEntry)
|
||||||
|
throws IOException {
|
||||||
|
this(linkedEntry.linkingCentralDirectoryHeader(name), file, linkedEntry.getSource(), storage, linkedEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CentralDirectoryHeader linkingCentralDirectoryHeader(String name) {
|
||||||
|
boolean encodeWithUtf8 = !EncodeUtils.canAsciiEncode(name);
|
||||||
|
GPFlags flags = GPFlags.make(encodeWithUtf8);
|
||||||
|
return cdh.link(name, EncodeUtils.encode(name, flags), flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
StoredEntry(
|
||||||
|
CentralDirectoryHeader header,
|
||||||
|
ZFile file,
|
||||||
|
@Nullable ProcessedAndRawByteSources source,
|
||||||
|
ByteStorage storage,
|
||||||
|
StoredEntry linkedEntry)
|
||||||
|
throws IOException {
|
||||||
cdh = header;
|
cdh = header;
|
||||||
this.file = file;
|
this.file = file;
|
||||||
deleted = false;
|
deleted = false;
|
||||||
verifyLog = file.makeVerifyLog();
|
verifyLog = file.makeVerifyLog();
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
|
this.linkedEntry = linkedEntry;
|
||||||
|
|
||||||
if (header.getOffset() >= 0) {
|
if (header.getOffset() >= 0) {
|
||||||
readLocalHeader();
|
readLocalHeader();
|
||||||
|
|
@ -579,6 +612,7 @@ public class StoredEntry {
|
||||||
ProcessedAndRawByteSources oldSource = source;
|
ProcessedAndRawByteSources oldSource = source;
|
||||||
source = createSourceFromZip(zipFileOffset);
|
source = createSourceFromZip(zipFileOffset);
|
||||||
cdh.setOffset(zipFileOffset);
|
cdh.setOffset(zipFileOffset);
|
||||||
|
if (!isLinkingEntry())
|
||||||
oldSource.close();
|
oldSource.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -667,6 +701,11 @@ public class StoredEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeData(ByteBuffer out) throws IOException {
|
private void writeData(ByteBuffer out) throws IOException {
|
||||||
|
writeData(out, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeData(ByteBuffer out, int extraOffset) throws IOException {
|
||||||
|
Preconditions.checkArgument(extraOffset >= 0 , "extraOffset < 0");
|
||||||
CentralDirectoryHeaderCompressInfo compressInfo = cdh.getCompressionInfoWithWait();
|
CentralDirectoryHeaderCompressInfo compressInfo = cdh.getCompressionInfoWithWait();
|
||||||
|
|
||||||
F_LOCAL_SIGNATURE.write(out);
|
F_LOCAL_SIGNATURE.write(out);
|
||||||
|
|
@ -686,7 +725,7 @@ public class StoredEntry {
|
||||||
F_COMPRESSED_SIZE.write(out, compressInfo.getCompressedSize());
|
F_COMPRESSED_SIZE.write(out, compressInfo.getCompressedSize());
|
||||||
F_UNCOMPRESSED_SIZE.write(out, cdh.getUncompressedSize());
|
F_UNCOMPRESSED_SIZE.write(out, cdh.getUncompressedSize());
|
||||||
F_FILE_NAME_LENGTH.write(out, cdh.getEncodedFileName().length);
|
F_FILE_NAME_LENGTH.write(out, cdh.getEncodedFileName().length);
|
||||||
F_EXTRA_LENGTH.write(out, localExtra.size());
|
F_EXTRA_LENGTH.write(out, localExtra.size() + extraOffset);
|
||||||
|
|
||||||
out.put(cdh.getEncodedFileName());
|
out.put(cdh.getEncodedFileName());
|
||||||
localExtra.write(out);
|
localExtra.write(out);
|
||||||
|
|
@ -706,9 +745,15 @@ public class StoredEntry {
|
||||||
public boolean realign() throws IOException {
|
public boolean realign() throws IOException {
|
||||||
Preconditions.checkState(!deleted, "Entry has been deleted.");
|
Preconditions.checkState(!deleted, "Entry has been deleted.");
|
||||||
|
|
||||||
|
if (isLinkingEntry()) return true;
|
||||||
|
|
||||||
return file.realign(this);
|
return file.realign(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isLinkingEntry() {
|
||||||
|
return linkedEntry != null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtains the contents of the local extra field.
|
* Obtains the contents of the local extra field.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -284,6 +284,11 @@ public class ZFile implements Closeable {
|
||||||
*/
|
*/
|
||||||
private final List<StoredEntry> uncompressedEntries;
|
private final List<StoredEntry> uncompressedEntries;
|
||||||
|
|
||||||
|
/** LSPatch
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private final List<StoredEntry> linkingEntries;
|
||||||
|
|
||||||
/** Current state of the zip file. */
|
/** Current state of the zip file. */
|
||||||
private ZipFileState state;
|
private ZipFileState state;
|
||||||
|
|
||||||
|
|
@ -449,6 +454,7 @@ public class ZFile implements Closeable {
|
||||||
|
|
||||||
entries = Maps.newHashMap();
|
entries = Maps.newHashMap();
|
||||||
uncompressedEntries = Lists.newArrayList();
|
uncompressedEntries = Lists.newArrayList();
|
||||||
|
linkingEntries = Lists.newArrayList();
|
||||||
extraDirectoryOffset = 0;
|
extraDirectoryOffset = 0;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -580,6 +586,10 @@ public class ZFile implements Closeable {
|
||||||
entries.put(uncompressed.getCentralDirectoryHeader().getName(), uncompressed);
|
entries.put(uncompressed.getCentralDirectoryHeader().getName(), uncompressed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (StoredEntry linking: linkingEntries) {
|
||||||
|
entries.put(linking.getCentralDirectoryHeader().getName(), linking);
|
||||||
|
}
|
||||||
|
|
||||||
return Sets.newHashSet(entries.values());
|
return Sets.newHashSet(entries.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1398,7 +1408,15 @@ public class ZFile implements Closeable {
|
||||||
|
|
||||||
int r;
|
int r;
|
||||||
// Put header data to the beginning of buffer
|
// Put header data to the beginning of buffer
|
||||||
|
// LSPatch: write extra entries in the extra field if it's a linking
|
||||||
|
int localHeaderSize = entry.getLocalHeaderSize();
|
||||||
|
for (var segment : entry.getLocalExtra().getSegments()) {
|
||||||
|
if (segment instanceof ExtraField.LinkingEntrySegment) {
|
||||||
|
((ExtraField.LinkingEntrySegment) segment).setOffset(localHeaderSize, offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
int readOffset = entry.toHeaderData(chunk);
|
int readOffset = entry.toHeaderData(chunk);
|
||||||
|
assert localHeaderSize == readOffset;
|
||||||
long writeOffset = offset;
|
long writeOffset = offset;
|
||||||
try (InputStream is = entry.getSource().getRawByteSource().openStream()) {
|
try (InputStream is = entry.getSource().getRawByteSource().openStream()) {
|
||||||
while ((r = is.read(chunk, readOffset, chunk.length - readOffset)) >= 0 || readOffset > 0) {
|
while ((r = is.read(chunk, readOffset, chunk.length - readOffset)) >= 0 || readOffset > 0) {
|
||||||
|
|
@ -1433,6 +1451,8 @@ public class ZFile implements Closeable {
|
||||||
newStored.add(mapEntry.getStore());
|
newStored.add(mapEntry.getStore());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newStored.addAll(linkingEntries);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Make sure we truncate the map before computing the central directory's location since
|
* Make sure we truncate the map before computing the central directory's location since
|
||||||
* the central directory is the last part of the file.
|
* the central directory is the last part of the file.
|
||||||
|
|
@ -1527,7 +1547,7 @@ public class ZFile implements Closeable {
|
||||||
|
|
||||||
dirStart = directoryEntry.getStart();
|
dirStart = directoryEntry.getStart();
|
||||||
dirSize = directoryEntry.getSize();
|
dirSize = directoryEntry.getSize();
|
||||||
Verify.verify(directory.getEntries().size() == entries.size());
|
Verify.verify(directory.getEntries().size() == entries.size() + linkingEntries.size());
|
||||||
} else {
|
} else {
|
||||||
/*
|
/*
|
||||||
* If we do not have a directory, then we must leave any requested offset empty.
|
* If we do not have a directory, then we must leave any requested offset empty.
|
||||||
|
|
@ -1707,8 +1727,8 @@ public class ZFile implements Closeable {
|
||||||
* @throws IOException failed to read the source data
|
* @throws IOException failed to read the source data
|
||||||
* @throws IllegalStateException if the file is in read-only mode
|
* @throws IllegalStateException if the file is in read-only mode
|
||||||
*/
|
*/
|
||||||
public void add(String name, InputStream stream, boolean mayCompress) throws IOException {
|
public StoredEntry add(String name, InputStream stream, boolean mayCompress) throws IOException {
|
||||||
add(name, storage.fromStream(stream), mayCompress);
|
return add(name, storage.fromStream(stream), mayCompress);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1735,7 +1755,7 @@ public class ZFile implements Closeable {
|
||||||
add(name, new CloseableDelegateByteSource(source, sizeBytes.get()), mayCompress);
|
add(name, new CloseableDelegateByteSource(source, sizeBytes.get()), mayCompress);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void add(String name, CloseableByteSource source, boolean mayCompress)
|
private StoredEntry add(String name, CloseableByteSource source, boolean mayCompress)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
checkNotInReadOnlyMode();
|
checkNotInReadOnlyMode();
|
||||||
|
|
||||||
|
|
@ -1744,9 +1764,25 @@ public class ZFile implements Closeable {
|
||||||
*/
|
*/
|
||||||
processAllReadyEntries();
|
processAllReadyEntries();
|
||||||
|
|
||||||
add(makeStoredEntry(name, source, mayCompress));
|
return add(makeStoredEntry(name, source, mayCompress));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addLink(String name, StoredEntry linkedEntry)
|
||||||
|
throws CloneNotSupportedException,IOException {
|
||||||
|
Preconditions.checkArgument(linkedEntry != null, "linkedEntry is null");
|
||||||
|
Preconditions.checkArgument(linkedEntry.getCentralDirectoryHeader().getOffset() < 0, "linkedEntry is not new file");
|
||||||
|
Preconditions.checkArgument(!linkedEntry.isLinkingEntry(), "linkedEntry is a linking entry");
|
||||||
|
var linkingEntry = new StoredEntry(name, this, storage, linkedEntry);
|
||||||
|
linkingEntries.add(linkingEntry);
|
||||||
|
linkedEntry.setLocalExtraNoNotify(new ExtraField(ImmutableList.<ExtraField.Segment>builder().add(linkedEntry.getLocalExtra().getSegments().toArray(new ExtraField.Segment[0])).add(new ExtraField.LinkingEntrySegment(linkingEntry)).build()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addNestedZipWithLinks(Map<String, String> nameMapping)
|
||||||
|
throws CloneNotSupportedException,IOException {
|
||||||
|
// TODO: nested link
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a {@link StoredEntry} to the zip. The entry is not immediately added to {@link #entries}
|
* Adds a {@link StoredEntry} to the zip. The entry is not immediately added to {@link #entries}
|
||||||
* because data may not yet be available. Instead, it is placed under {@link #uncompressedEntries}
|
* because data may not yet be available. Instead, it is placed under {@link #uncompressedEntries}
|
||||||
|
|
@ -1760,9 +1796,10 @@ public class ZFile implements Closeable {
|
||||||
* @throws IOException failed to process this entry (or a previous one whose future only completed
|
* @throws IOException failed to process this entry (or a previous one whose future only completed
|
||||||
* now)
|
* now)
|
||||||
*/
|
*/
|
||||||
private void add(final StoredEntry newEntry) throws IOException {
|
private StoredEntry add(final StoredEntry newEntry) throws IOException {
|
||||||
uncompressedEntries.add(newEntry);
|
uncompressedEntries.add(newEntry);
|
||||||
processAllReadyEntries();
|
processAllReadyEntries();
|
||||||
|
return newEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -225,7 +225,8 @@ public class LSPatch {
|
||||||
for (String arch : APK_LIB_PATH_ARRAY) {
|
for (String arch : APK_LIB_PATH_ARRAY) {
|
||||||
String entryName = "assets/lib/lspd/" + arch + "/liblspd.so";
|
String entryName = "assets/lib/lspd/" + arch + "/liblspd.so";
|
||||||
try (var is = getClass().getClassLoader().getResourceAsStream("assets/so/" + (arch.equals("arm") ? "armeabi-v7a" : (arch.equals("arm64") ? "arm64-v8a" : arch)) + "/liblspd.so")) {
|
try (var is = getClass().getClassLoader().getResourceAsStream("assets/so/" + (arch.equals("arm") ? "armeabi-v7a" : (arch.equals("arm64") ? "arm64-v8a" : arch)) + "/liblspd.so")) {
|
||||||
dstZFile.add(entryName, is, false); // no compress for so
|
var entry = dstZFile.add(entryName, is, false); // no compress for so
|
||||||
|
dstZFile.addLink("assets/lib/lspd/test/" + arch + "/liblspd.so", entry);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
// More exception info
|
// More exception info
|
||||||
throw new PatchError("Error when adding native lib", e);
|
throw new PatchError("Error when adding native lib", e);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue