diff --git a/apkzlib/src/main/java/com/android/tools/build/apkzlib/sign/package-info.java b/apkzlib/src/main/java/com/android/tools/build/apkzlib/sign/package-info.java
index 5c2536e..480017e 100644
--- a/apkzlib/src/main/java/com/android/tools/build/apkzlib/sign/package-info.java
+++ b/apkzlib/src/main/java/com/android/tools/build/apkzlib/sign/package-info.java
@@ -148,8 +148,8 @@
*
Additionally, by adding/removing extensions we can configure what type of apk we want:
*
*
- * - No SigningExtension ⇒ Aligned, unsigned apk.
- *
- SigningExtension ⇒ Aligned, signed apk.
+ *
- No SigningExtension => Aligned, unsigned apk.
+ *
- SigningExtension => Aligned, signed apk.
*
*
* So, by configuring which extensions to add, the package task can decide what type of apk we want.
diff --git a/apkzlib/src/main/java/com/android/tools/build/apkzlib/zip/CentralDirectoryHeader.java b/apkzlib/src/main/java/com/android/tools/build/apkzlib/zip/CentralDirectoryHeader.java
index 02f0c5c..80826db 100644
--- a/apkzlib/src/main/java/com/android/tools/build/apkzlib/zip/CentralDirectoryHeader.java
+++ b/apkzlib/src/main/java/com/android/tools/build/apkzlib/zip/CentralDirectoryHeader.java
@@ -152,6 +152,25 @@ public class CentralDirectoryHeader implements Cloneable {
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.
*
diff --git a/apkzlib/src/main/java/com/android/tools/build/apkzlib/zip/ExtraField.java b/apkzlib/src/main/java/com/android/tools/build/apkzlib/zip/ExtraField.java
index 6a6a356..0dfca1e 100644
--- a/apkzlib/src/main/java/com/android/tools/build/apkzlib/zip/ExtraField.java
+++ b/apkzlib/src/main/java/com/android/tools/build/apkzlib/zip/ExtraField.java
@@ -51,6 +51,9 @@ public class ExtraField {
/** Header ID for field with zip alignment. */
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
* non-{@code null}.
@@ -382,4 +385,43 @@ public class ExtraField {
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);
+ }
+ }
}
diff --git a/apkzlib/src/main/java/com/android/tools/build/apkzlib/zip/StoredEntry.java b/apkzlib/src/main/java/com/android/tools/build/apkzlib/zip/StoredEntry.java
index 1abe997..77167d2 100644
--- a/apkzlib/src/main/java/com/android/tools/build/apkzlib/zip/StoredEntry.java
+++ b/apkzlib/src/main/java/com/android/tools/build/apkzlib/zip/StoredEntry.java
@@ -25,6 +25,7 @@ import com.google.common.base.Preconditions;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.base.Verify;
+import com.google.common.collect.ImmutableList;
import com.google.common.io.ByteStreams;
import com.google.common.primitives.Ints;
import java.io.BufferedInputStream;
@@ -154,6 +155,12 @@ public class StoredEntry {
/** Storage used to create buffers when loading storage into memory. */
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.
*
@@ -164,17 +171,43 @@ public class StoredEntry {
* from the zip file, that is, if {@code header.getOffset()} is non-negative
* @throws IOException failed to create the entry
*/
+ StoredEntry(
+ CentralDirectoryHeader header,
+ ZFile file,
+ @Nullable ProcessedAndRawByteSources source,
+ ByteStorage storage)
+ 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)
+ ByteStorage storage,
+ StoredEntry linkedEntry)
throws IOException {
cdh = header;
this.file = file;
deleted = false;
verifyLog = file.makeVerifyLog();
this.storage = storage;
+ this.linkedEntry = linkedEntry;
if (header.getOffset() >= 0) {
readLocalHeader();
@@ -579,7 +612,8 @@ public class StoredEntry {
ProcessedAndRawByteSources oldSource = source;
source = createSourceFromZip(zipFileOffset);
cdh.setOffset(zipFileOffset);
- oldSource.close();
+ if (!isLinkingEntry())
+ oldSource.close();
}
/**
@@ -667,6 +701,11 @@ public class StoredEntry {
}
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();
F_LOCAL_SIGNATURE.write(out);
@@ -686,7 +725,7 @@ public class StoredEntry {
F_COMPRESSED_SIZE.write(out, compressInfo.getCompressedSize());
F_UNCOMPRESSED_SIZE.write(out, cdh.getUncompressedSize());
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());
localExtra.write(out);
@@ -706,9 +745,15 @@ public class StoredEntry {
public boolean realign() throws IOException {
Preconditions.checkState(!deleted, "Entry has been deleted.");
+ if (isLinkingEntry()) return true;
+
return file.realign(this);
}
+ public boolean isLinkingEntry() {
+ return linkedEntry != null;
+ }
+
/**
* Obtains the contents of the local extra field.
*
diff --git a/apkzlib/src/main/java/com/android/tools/build/apkzlib/zip/ZFile.java b/apkzlib/src/main/java/com/android/tools/build/apkzlib/zip/ZFile.java
index 54d0d9e..bcc23fc 100644
--- a/apkzlib/src/main/java/com/android/tools/build/apkzlib/zip/ZFile.java
+++ b/apkzlib/src/main/java/com/android/tools/build/apkzlib/zip/ZFile.java
@@ -284,6 +284,11 @@ public class ZFile implements Closeable {
*/
private final List uncompressedEntries;
+ /** LSPatch
+ *
+ */
+ private final List linkingEntries;
+
/** Current state of the zip file. */
private ZipFileState state;
@@ -449,6 +454,7 @@ public class ZFile implements Closeable {
entries = Maps.newHashMap();
uncompressedEntries = Lists.newArrayList();
+ linkingEntries = Lists.newArrayList();
extraDirectoryOffset = 0;
try {
@@ -580,6 +586,10 @@ public class ZFile implements Closeable {
entries.put(uncompressed.getCentralDirectoryHeader().getName(), uncompressed);
}
+ for (StoredEntry linking: linkingEntries) {
+ entries.put(linking.getCentralDirectoryHeader().getName(), linking);
+ }
+
return Sets.newHashSet(entries.values());
}
@@ -1398,7 +1408,15 @@ public class ZFile implements Closeable {
int r;
// 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);
+ assert localHeaderSize == readOffset;
long writeOffset = offset;
try (InputStream is = entry.getSource().getRawByteSource().openStream()) {
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.addAll(linkingEntries);
+
/*
* Make sure we truncate the map before computing the central directory's location since
* the central directory is the last part of the file.
@@ -1527,7 +1547,7 @@ public class ZFile implements Closeable {
dirStart = directoryEntry.getStart();
dirSize = directoryEntry.getSize();
- Verify.verify(directory.getEntries().size() == entries.size());
+ Verify.verify(directory.getEntries().size() == entries.size() + linkingEntries.size());
} else {
/*
* 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 IllegalStateException if the file is in read-only mode
*/
- public void add(String name, InputStream stream, boolean mayCompress) throws IOException {
- add(name, storage.fromStream(stream), mayCompress);
+ public StoredEntry add(String name, InputStream stream, boolean mayCompress) throws IOException {
+ return add(name, storage.fromStream(stream), mayCompress);
}
/**
@@ -1735,7 +1755,7 @@ public class ZFile implements Closeable {
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 {
checkNotInReadOnlyMode();
@@ -1744,9 +1764,25 @@ public class ZFile implements Closeable {
*/
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.builder().add(linkedEntry.getLocalExtra().getSegments().toArray(new ExtraField.Segment[0])).add(new ExtraField.LinkingEntrySegment(linkingEntry)).build()));
+ }
+
+ public void addNestedZipWithLinks(Map nameMapping)
+ throws CloneNotSupportedException,IOException {
+ // TODO: nested link
+ }
+
+
/**
* 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}
@@ -1760,9 +1796,10 @@ public class ZFile implements Closeable {
* @throws IOException failed to process this entry (or a previous one whose future only completed
* now)
*/
- private void add(final StoredEntry newEntry) throws IOException {
+ private StoredEntry add(final StoredEntry newEntry) throws IOException {
uncompressedEntries.add(newEntry);
processAllReadyEntries();
+ return newEntry;
}
/**
diff --git a/patch/src/main/java/org/lsposed/patch/LSPatch.java b/patch/src/main/java/org/lsposed/patch/LSPatch.java
index 03afa44..23d4c2e 100644
--- a/patch/src/main/java/org/lsposed/patch/LSPatch.java
+++ b/patch/src/main/java/org/lsposed/patch/LSPatch.java
@@ -225,7 +225,8 @@ public class LSPatch {
for (String arch : APK_LIB_PATH_ARRAY) {
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")) {
- 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) {
// More exception info
throw new PatchError("Error when adding native lib", e);