com.mcxiaoke.packer.support.walle.PayloadWriter Maven / Gradle / Ivy
package com.mcxiaoke.packer.support.walle;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
final class PayloadWriter {
private PayloadWriter() {
super();
}
public static void writeBlock(File apkFile, final int id,
final byte[] bytes) throws IOException {
final ByteBuffer byteBuffer = ByteBuffer.allocate(bytes.length);
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
byteBuffer.put(bytes, 0, bytes.length);
byteBuffer.flip();
writeBlock(apkFile, id, byteBuffer);
}
public static void writeBlock(final File apkFile, final int id,
final ByteBuffer buffer) throws IOException {
final Map idValues = new HashMap<>();
idValues.put(id, buffer);
writeValues(apkFile, idValues);
}
/**
* writeBlock new idValues into apk, update if id exists
* NOTE: use unknown IDs. DO NOT use ID that have already been used. See APK Signature Scheme v2
*/
private static void writeValues(final File apkFile, final Map idValues) throws IOException {
final ApkSigningBlockHandler handler = new ApkSigningBlockHandler() {
@Override
public ApkSigningBlock handle(final Map originIdValues) {
if (idValues != null && !idValues.isEmpty()) {
originIdValues.putAll(idValues);
}
final ApkSigningBlock apkSigningBlock = new ApkSigningBlock();
final Set> entrySet = originIdValues.entrySet();
for (Map.Entry entry : entrySet) {
final ApkSigningPayload payload = new ApkSigningPayload(entry.getKey(), entry.getValue());
apkSigningBlock.addPayload(payload);
}
return apkSigningBlock;
}
};
writeApkSigningBlock(apkFile, handler);
}
static void writeApkSigningBlock(final File apkFile, final ApkSigningBlockHandler handler) throws IOException {
RandomAccessFile raf = null;
FileChannel fc = null;
try {
raf = new RandomAccessFile(apkFile, "rw");
fc = raf.getChannel();
final long commentLength = ApkUtil.findZipCommentLength(fc);
final long centralDirStartOffset = ApkUtil.findCentralDirStartOffset(fc, commentLength);
// Find the APK Signing Block. The block immediately precedes the Central Directory.
final Pair apkSigningBlockAndOffset = ApkUtil.findApkSigningBlock(fc, centralDirStartOffset);
final ByteBuffer apkSigningBlock2 = apkSigningBlockAndOffset.getFirst();
final long apkSigningBlockOffset = apkSigningBlockAndOffset.getSecond();
if (centralDirStartOffset == 0 || apkSigningBlockOffset == 0) {
throw new IOException(
"No APK Signature Scheme v2 block in APK Signing Block");
}
final Map originIdValues = ApkUtil.findIdValues(apkSigningBlock2);
// Find the APK Signature Scheme v2 Block inside the APK Signing Block.
final ByteBuffer apkSignatureSchemeV2Block = originIdValues.get(V2Const.APK_SIGNATURE_SCHEME_V2_BLOCK_ID);
if (apkSignatureSchemeV2Block == null) {
throw new IOException(
"No APK Signature Scheme v2 block in APK Signing Block");
}
final ApkSigningBlock apkSigningBlock = handler.handle(originIdValues);
// read CentralDir
raf.seek(centralDirStartOffset);
final byte[] centralDirBytes = new byte[(int) (fc.size() - centralDirStartOffset)];
raf.read(centralDirBytes);
fc.position(apkSigningBlockOffset);
final long length = apkSigningBlock.writeTo(raf);
// store CentralDir
raf.write(centralDirBytes);
// update length
raf.setLength(raf.getFilePointer());
// update CentralDir Offset
// End of central directory record (EOCD)
// Offset Bytes Description[23]
// 0 4 End of central directory signature = 0x06054b50
// 4 2 Number of this disk
// 6 2 Disk where central directory starts
// 8 2 Number of central directory records on this disk
// 10 2 Total number of central directory records
// 12 4 Size of central directory (bytes)
// 16 4 Offset of start of central directory, relative to start of archive
// 20 2 Comment length (n)
// 22 n Comment
raf.seek(fc.size() - commentLength - 6);
// 6 = 2(Comment length) + 4
// (Offset of start of central directory, relative to start of archive)
final ByteBuffer temp = ByteBuffer.allocate(4);
temp.order(ByteOrder.LITTLE_ENDIAN);
temp.putInt((int) (centralDirStartOffset + length + 8 - (centralDirStartOffset - apkSigningBlockOffset)));
// 8 = size of block in bytes (excluding this field) (uint64)
temp.flip();
raf.write(temp.array());
} finally {
V2Utils.close(fc);
V2Utils.close(raf);
}
}
interface ApkSigningBlockHandler {
ApkSigningBlock handle(Map originIdValues);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy