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: * *

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