mirror of
https://github.com/revanced/Apktool.git
synced 2025-05-02 23:04:26 +02:00
feat: decode 9patch
files on Android
This commit is contained in:
parent
4608df636e
commit
72ffcbbc44
@ -11,6 +11,20 @@ val xmlunitVersion: String by rootProject.extra
|
||||
val gitRevision: String by rootProject.extra
|
||||
val apktoolVersion: String by rootProject.extra
|
||||
|
||||
// region Determine Android SDK location
|
||||
|
||||
val sdkRoot: String? = System.getenv("ANDROID_SDK_ROOT")
|
||||
val androidJarPath: String = if (sdkRoot == null) {
|
||||
GradleException("Missing ANDROID_SDK_ROOT").printStackTrace()
|
||||
|
||||
"com.google.android:android:4.1.1.4"
|
||||
} else {
|
||||
val androidVersion = 33
|
||||
File("$sdkRoot/platforms/android-$androidVersion/android.jar").path
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
tasks {
|
||||
processResources {
|
||||
from("src/main/resources/properties") {
|
||||
@ -49,4 +63,6 @@ dependencies {
|
||||
|
||||
testImplementation("junit:junit:$junitVersion")
|
||||
testImplementation("org.xmlunit:xmlunit-legacy:$xmlunitVersion")
|
||||
|
||||
compileOnly(files(androidJarPath))
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ import brut.androlib.res.xml.ResXmlPatcher;
|
||||
import brut.directory.Directory;
|
||||
import brut.directory.DirectoryException;
|
||||
import brut.directory.FileDirectory;
|
||||
import brut.util.OSDetection;
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
import java.io.*;
|
||||
@ -148,7 +149,11 @@ public class ResourcesDecoder {
|
||||
|
||||
ResStreamDecoderContainer decoders = new ResStreamDecoderContainer();
|
||||
decoders.setDecoder("raw", new ResRawStreamDecoder());
|
||||
decoders.setDecoder("9patch", new Res9patchStreamDecoder());
|
||||
|
||||
decoders.setDecoder(
|
||||
"9patch",
|
||||
OSDetection.isAndroid() ? new Res9patchAndroidStreamDecoder() : new Res9patchStreamDecoder()
|
||||
);
|
||||
|
||||
AXmlResourceParser axmlParser = new AXmlResourceParser(mResTable);
|
||||
decoders.setDecoder("xml", new XmlPullStreamDecoder(axmlParser, getResXmlSerializer()));
|
||||
|
@ -0,0 +1,135 @@
|
||||
package brut.androlib.res.decoder;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import brut.androlib.exceptions.AndrolibException;
|
||||
import brut.androlib.exceptions.CantFind9PatchChunkException;
|
||||
import brut.androlib.res.data.ninepatch.NinePatchData;
|
||||
import brut.androlib.res.data.ninepatch.OpticalInset;
|
||||
import brut.util.ExtDataInput;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
public class Res9patchAndroidStreamDecoder implements ResStreamDecoder {
|
||||
public void decode(InputStream in, OutputStream out) throws AndrolibException {
|
||||
try {
|
||||
byte[] data = IOUtils.toByteArray(in);
|
||||
|
||||
if (data.length == 0) {
|
||||
return;
|
||||
}
|
||||
Bitmap bm = BitmapFactory.decodeByteArray(data, 0, data.length);
|
||||
int width = bm.getWidth(), height = bm.getHeight();
|
||||
|
||||
Bitmap outImg = Bitmap.createBitmap(width + 2, height + 2, bm.getConfig());
|
||||
|
||||
for (int w = 0; w < width; w++)
|
||||
for (int h = 0; h < height; h++) outImg.setPixel(w + 1, h + 1, bm.getPixel(w, h));
|
||||
|
||||
NinePatchData np = getNinePatch(data);
|
||||
drawHLineA(outImg, height + 1, np.padLeft + 1, width - np.padRight);
|
||||
drawVLineA(outImg, width + 1, np.padTop + 1, height - np.padBottom);
|
||||
|
||||
int[] xDivs = np.xDivs;
|
||||
if (xDivs.length == 0) {
|
||||
drawHLineA(outImg, 0, 1, width);
|
||||
} else {
|
||||
for (int i = 0; i < xDivs.length; i += 2) {
|
||||
drawHLineA(outImg, 0, xDivs[i] + 1, xDivs[i + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
int[] yDivs = np.yDivs;
|
||||
if (yDivs.length == 0) {
|
||||
drawVLineA(outImg, 0, 1, height);
|
||||
} else {
|
||||
for (int i = 0; i < yDivs.length; i += 2) {
|
||||
drawVLineA(outImg, 0, yDivs[i] + 1, yDivs[i + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Some images additionally use Optical Bounds
|
||||
// https://developer.android.com/about/versions/android-4.3.html#OpticalBounds
|
||||
try {
|
||||
OpticalInset oi = getOpticalInset(data);
|
||||
|
||||
for (int i = 0; i < oi.layoutBoundsLeft; i++) {
|
||||
int x = 1 + i;
|
||||
outImg.setPixel(x, height + 1, OI_COLOR);
|
||||
}
|
||||
|
||||
for (int i = 0; i < oi.layoutBoundsRight; i++) {
|
||||
int x = width - i;
|
||||
outImg.setPixel(x, height + 1, OI_COLOR);
|
||||
}
|
||||
|
||||
for (int i = 0; i < oi.layoutBoundsTop; i++) {
|
||||
int y = 1 + i;
|
||||
outImg.setPixel(width + 1, y, OI_COLOR);
|
||||
}
|
||||
|
||||
for (int i = 0; i < oi.layoutBoundsBottom; i++) {
|
||||
int y = height - i;
|
||||
outImg.setPixel(width + 1, y, OI_COLOR);
|
||||
}
|
||||
} catch (CantFind9PatchChunkException t) {
|
||||
// This chunk might not exist
|
||||
}
|
||||
|
||||
outImg.compress(Bitmap.CompressFormat.PNG, 100, out);
|
||||
bm.recycle();
|
||||
outImg.recycle();
|
||||
} catch (IOException ex) {
|
||||
throw new AndrolibException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private NinePatchData getNinePatch(byte[] data) throws AndrolibException,
|
||||
IOException {
|
||||
ExtDataInput di = new ExtDataInput(new ByteArrayInputStream(data));
|
||||
find9patchChunk(di, NP_CHUNK_TYPE);
|
||||
return NinePatchData.decode(di);
|
||||
}
|
||||
|
||||
private OpticalInset getOpticalInset(byte[] data) throws AndrolibException,
|
||||
IOException {
|
||||
ExtDataInput di = new ExtDataInput(new ByteArrayInputStream(data));
|
||||
find9patchChunk(di, OI_CHUNK_TYPE);
|
||||
return OpticalInset.decode(di);
|
||||
}
|
||||
|
||||
private void find9patchChunk(DataInput di, int magic) throws AndrolibException,
|
||||
IOException {
|
||||
di.skipBytes(8);
|
||||
while (true) {
|
||||
int size;
|
||||
try {
|
||||
size = di.readInt();
|
||||
} catch (IOException ex) {
|
||||
throw new CantFind9PatchChunkException("Cant find nine patch chunk", ex);
|
||||
}
|
||||
if (di.readInt() == magic) {
|
||||
return;
|
||||
}
|
||||
di.skipBytes(size + 4);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawHLineA(Bitmap bm, int y, int x1, int x2) {
|
||||
for (int x = x1; x <= x2; x++) {
|
||||
bm.setPixel(x, y, NP_COLOR);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawVLineA(Bitmap bm, int x, int y1, int y2) {
|
||||
for (int y = y1; y <= y2; y++) {
|
||||
bm.setPixel(x, y, NP_COLOR);
|
||||
}
|
||||
}
|
||||
|
||||
private static final int NP_CHUNK_TYPE = 0x6e705463; // npTc
|
||||
private static final int OI_CHUNK_TYPE = 0x6e704c62; // npLb
|
||||
private static final int NP_COLOR = 0xff000000;
|
||||
private static final int OI_COLOR = 0xffff0000;
|
||||
}
|
@ -18,10 +18,13 @@ package brut.androlib.decode;
|
||||
|
||||
import brut.androlib.BaseTest;
|
||||
import brut.androlib.TestUtils;
|
||||
import brut.androlib.res.decoder.Res9patchAndroidStreamDecoder;
|
||||
import brut.androlib.res.decoder.Res9patchStreamDecoder;
|
||||
import brut.androlib.res.decoder.ResStreamDecoder;
|
||||
import brut.common.BrutException;
|
||||
import brut.directory.ExtFile;
|
||||
import brut.util.OS;
|
||||
import brut.util.OSDetection;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
@ -51,7 +54,7 @@ public class MissingDiv9PatchTest extends BaseTest {
|
||||
InputStream inputStream = getFileInputStream();
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
|
||||
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
|
||||
ResStreamDecoder decoder = OSDetection.isAndroid() ? new Res9patchAndroidStreamDecoder() : new Res9patchStreamDecoder();
|
||||
decoder.decode(inputStream, outputStream);
|
||||
|
||||
BufferedImage image = ImageIO.read(new ByteArrayInputStream(outputStream.toByteArray()));
|
||||
|
@ -32,6 +32,15 @@ public class OSDetection {
|
||||
return (OS.contains("nix") || OS.contains("nux") || OS.contains("aix") || (OS.contains("sunos")));
|
||||
}
|
||||
|
||||
public static boolean isAndroid() {
|
||||
try {
|
||||
Class.forName("android.app.Activity");
|
||||
return true;
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean is64Bit() {
|
||||
if (isWindows()) {
|
||||
String arch = System.getenv("PROCESSOR_ARCHITECTURE");
|
||||
|
Loading…
x
Reference in New Issue
Block a user