blob: 0a633f3c15a535449bb2015118157d5cda184c35 [file] [log] [blame]
// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.dex;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import javax.imageio.ImageIO;
import org.apache.commons.compress.compressors.CompressorException;
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
public class BSPatch {
private final static char[] BSDIFF_MAGIC = "BSDIFF40".toCharArray();
private final static int BSDIFF_HEADER_LENGTH = 32;
private final ByteBuffer patchInput;
private final ByteBuffer oldInput;
private final Path output;
private final Path dexPath;
private InputStream controlStream;
private InputStream diffStream;
private InputStream extraStream;
private long controlBytesRead;
private long diffBytesRead;
private long extraBytesRead;
private int controlBlockLen;
private int diffBlockLen;
private int extraBlockLen;
public static void main(String[] args) {
boolean imageMode = args.length > 1 && args[0].equals("-i");
int argOffset = imageMode ? 1 : 0;
if (args.length < argOffset + 3) {
System.out.println("Usage: [-i] <patch file> <original dex> <output file> [target dex]");
System.exit(1);
}
try {
new BSPatch(Paths.get(args[argOffset]), Paths.get(args[argOffset + 1]),
Paths.get(args[argOffset + 2]),
args.length != argOffset + 4 ? null : Paths.get(args[argOffset + 3]))
.apply(imageMode);
} catch (IOException e) {
System.err.println("File I/O error: " + e.toString());
} catch (CompressorException e) {
System.err.println("BZIP error: " + e.toString());
}
}
private BSPatch(Path patchInput, Path oldInput, Path output, Path dexPath) throws IOException {
this.patchInput = ByteBuffer.wrap(Files.readAllBytes(patchInput));
this.oldInput = ByteBuffer.wrap(Files.readAllBytes(oldInput));
this.output = output;
this.dexPath = dexPath;
}
public void apply(boolean imageMode) throws CompressorException, IOException {
PatchExecutor executor = imageMode ? new ImageExecutor(dexPath) : new FileExecutor();
checkHeader();
setupSegmentsAndOutput(executor);
processControl(executor);
executor.writeResult();
printStats();
}
private int percentOf(long a, long b) {
return (int) ((((double) a) / ((double) b)) * 100);
}
private void printStats() {
System.out.println("Size of control block (compressed bytes): " + controlBlockLen);
System.out.println("Size of control block (read bytes): " + controlBytesRead);
System.out
.println("Compression of control block: " + percentOf(controlBlockLen, controlBytesRead));
System.out.println("Size of diff data block (compressed bytes): " + diffBlockLen);
System.out.println("Size of diff data block (read bytes): " + diffBytesRead);
System.out
.println("Compression of diff data block: " + percentOf(diffBlockLen, diffBytesRead));
System.out.println("Size of extra data block (compressed bytes): " + extraBlockLen);
System.out.println("Size of extra data block (read bytes): " + extraBytesRead);
System.out
.println("Compression of extra data block: " + percentOf(extraBlockLen, extraBytesRead));
}
private void processControl(PatchExecutor executor) throws IOException {
int blockSize;
while ((blockSize = readNextControlEntry()) != Integer.MIN_VALUE) {
int extraSize = readNextControlEntry();
int advanceOld = readNextControlEntry();
executor.copyDiff(blockSize);
executor.copyOld(blockSize);
executor.submitBlock(blockSize);
executor.copyExtra(extraSize);
executor.skipOld(advanceOld);
}
}
private void checkHeader() {
for (int i = 0; i < BSDIFF_MAGIC.length; i++) {
if (patchInput.get() != BSDIFF_MAGIC[i]) {
throw new RuntimeException("Illegal patch, wrong magic!");
}
}
}
private void setupSegmentsAndOutput(PatchExecutor executor)
throws CompressorException, IOException {
controlBlockLen = readOffset();
diffBlockLen = readOffset();
int newFileSize = readOffset();
extraBlockLen =
patchInput.array().length - (BSDIFF_HEADER_LENGTH + controlBlockLen + diffBlockLen);
executor.createOutput(newFileSize);
controlStream = new BZip2CompressorInputStream(
new ByteArrayInputStream(patchInput.array(), BSDIFF_HEADER_LENGTH, controlBlockLen));
diffStream = new BZip2CompressorInputStream(
new ByteArrayInputStream(patchInput.array(), BSDIFF_HEADER_LENGTH + controlBlockLen,
diffBlockLen));
extraStream = new BZip2CompressorInputStream(
new ByteArrayInputStream(patchInput.array(),
BSDIFF_HEADER_LENGTH + controlBlockLen + diffBlockLen,
extraBlockLen));
}
private int readOffset() {
byte[] buffer = new byte[8];
patchInput.get(buffer);
return decodeOffset(buffer);
}
private int readNextControlEntry() throws IOException {
byte[] buffer = new byte[8];
int read = controlStream.read(buffer);
if (read == -1) {
return Integer.MIN_VALUE;
}
controlBytesRead += read;
assert read == buffer.length;
return decodeOffset(buffer);
}
private static int decodeOffset(byte[] buffer) {
long offset = buffer[7] & 0x7F;
for (int i = 6; i >= 0; i--) {
offset = (offset << 8) | (((int) buffer[i]) & 0xff);
}
if ((buffer[7] & 0x80) != 0) {
offset = -offset;
}
assert offset < Integer.MAX_VALUE && offset > Integer.MIN_VALUE;
return (int) offset;
}
private static abstract class PatchExecutor {
public abstract void createOutput(int newFileSize);
public abstract void copyDiff(int blockSize) throws IOException;
public abstract void copyOld(int blockSize) throws IOException;
public abstract void submitBlock(int blockSize);
public abstract void copyExtra(int extraSize) throws IOException;
public abstract void skipOld(int advanceOld) throws IOException;
public abstract void writeResult() throws IOException;
}
private class FileExecutor extends PatchExecutor {
private ByteBuffer resultBuffer;
private byte[] mergeBuffer = null;
@Override
public void createOutput(int newFileSize) {
resultBuffer = ByteBuffer.allocate(newFileSize);
}
@Override
public void copyDiff(int blockSize) throws IOException {
assert mergeBuffer == null;
mergeBuffer = new byte[blockSize];
int read = diffStream.read(mergeBuffer);
diffBytesRead += read;
assert read == blockSize;
}
@Override
public void copyOld(int blockSize) throws IOException {
assert mergeBuffer.length == blockSize;
byte[] data = new byte[blockSize];
oldInput.get(data);
for (int i = 0; i < mergeBuffer.length; i++) {
mergeBuffer[i] = (byte) ((((int) mergeBuffer[i]) & 0xff) + (((int) data[i]) & 0xff));
}
}
@Override
public void submitBlock(int blockSize) {
assert mergeBuffer != null;
assert mergeBuffer.length == blockSize;
resultBuffer.put(mergeBuffer);
mergeBuffer = null;
}
@Override
public void copyExtra(int extraSize) throws IOException {
byte[] data = new byte[extraSize];
int read = extraStream.read(data);
assert read == extraSize;
extraBytesRead += read;
resultBuffer.put(data);
}
@Override
public void skipOld(int delta) throws IOException {
oldInput.position(oldInput.position() + delta);
}
@Override
public void writeResult() throws IOException {
OutputStream outputStream = Files.newOutputStream(output);
outputStream.write(resultBuffer.array());
outputStream.close();
}
}
private class ImageExecutor extends PatchExecutor {
private final Path dexPath;
BufferedImage image;
int position = 0;
int width;
int height;
private ImageExecutor(Path dexPath) {
this.dexPath = dexPath;
}
@Override
public void createOutput(int newFileSize) {
int root = (int) Math.sqrt(newFileSize);
width = newFileSize / root;
height = newFileSize / width + 1;
image = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
}
@Override
public void copyDiff(int blockSize) throws IOException {
byte[] buffer = new byte[blockSize];
int read = diffStream.read(buffer);
assert read == blockSize;
diffBytesRead += read;
for (int i = 0; i < buffer.length; i++) {
if (buffer[i] != 0) {
int y = (position + i) / width;
int x = (position + i) % width;
int rgb = image.getRGB(x, y);
rgb = rgb | 0xFF0000;
image.setRGB(x, y, rgb);
}
}
}
@Override
public void copyOld(int blockSize) throws IOException {
for (int i = 0; i < blockSize; i++) {
int y = (position + i) / width;
int x = (position + i) % width;
int rgb = image.getRGB(x, y);
if ((rgb & 0xFF0000) == 0) {
rgb = rgb | 0xFF00;
}
image.setRGB(x, y, rgb);
}
}
@Override
public void submitBlock(int blockSize) {
position += blockSize;
}
@Override
public void copyExtra(int extraSize) throws IOException {
long skipped = extraStream.skip(extraSize);
assert skipped == extraSize;
extraBytesRead += skipped;
for (int i = 0; i < extraSize; i++) {
int y = (position + i) / width;
int x = (position + i) % width;
int rgb = image.getRGB(x, y);
rgb = rgb | 0xFF;
image.setRGB(x, y, rgb);
}
position += extraSize;
}
@Override
public void skipOld(int advanceOld) throws IOException {
}
@Override
public void writeResult() throws IOException {
if (dexPath != null) {
DexSection[] sections = DexParser.parseMapFrom(dexPath);
for (DexSection section : sections) {
int y = section.offset / width;
for (int x = 0; x < width; x++) {
int val = (x / 10) % 2 == 0 ? 0 : 0xffffff;
image.setRGB(x, y, val);
}
System.out.println(section);
}
}
ImageIO.write(image, "png", output.toFile());
}
}
}