mirror of
https://github.com/revanced/Apktool.git
synced 2025-05-03 15:24: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 gitRevision: String by rootProject.extra
|
||||||
val apktoolVersion: 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 {
|
tasks {
|
||||||
processResources {
|
processResources {
|
||||||
from("src/main/resources/properties") {
|
from("src/main/resources/properties") {
|
||||||
@ -49,4 +63,6 @@ dependencies {
|
|||||||
|
|
||||||
testImplementation("junit:junit:$junitVersion")
|
testImplementation("junit:junit:$junitVersion")
|
||||||
testImplementation("org.xmlunit:xmlunit-legacy:$xmlunitVersion")
|
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.Directory;
|
||||||
import brut.directory.DirectoryException;
|
import brut.directory.DirectoryException;
|
||||||
import brut.directory.FileDirectory;
|
import brut.directory.FileDirectory;
|
||||||
|
import brut.util.OSDetection;
|
||||||
import org.xmlpull.v1.XmlSerializer;
|
import org.xmlpull.v1.XmlSerializer;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
@ -148,7 +149,11 @@ public class ResourcesDecoder {
|
|||||||
|
|
||||||
ResStreamDecoderContainer decoders = new ResStreamDecoderContainer();
|
ResStreamDecoderContainer decoders = new ResStreamDecoderContainer();
|
||||||
decoders.setDecoder("raw", new ResRawStreamDecoder());
|
decoders.setDecoder("raw", new ResRawStreamDecoder());
|
||||||
decoders.setDecoder("9patch", new Res9patchStreamDecoder());
|
|
||||||
|
decoders.setDecoder(
|
||||||
|
"9patch",
|
||||||
|
OSDetection.isAndroid() ? new Res9patchAndroidStreamDecoder() : new Res9patchStreamDecoder()
|
||||||
|
);
|
||||||
|
|
||||||
AXmlResourceParser axmlParser = new AXmlResourceParser(mResTable);
|
AXmlResourceParser axmlParser = new AXmlResourceParser(mResTable);
|
||||||
decoders.setDecoder("xml", new XmlPullStreamDecoder(axmlParser, getResXmlSerializer()));
|
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.BaseTest;
|
||||||
import brut.androlib.TestUtils;
|
import brut.androlib.TestUtils;
|
||||||
|
import brut.androlib.res.decoder.Res9patchAndroidStreamDecoder;
|
||||||
import brut.androlib.res.decoder.Res9patchStreamDecoder;
|
import brut.androlib.res.decoder.Res9patchStreamDecoder;
|
||||||
|
import brut.androlib.res.decoder.ResStreamDecoder;
|
||||||
import brut.common.BrutException;
|
import brut.common.BrutException;
|
||||||
import brut.directory.ExtFile;
|
import brut.directory.ExtFile;
|
||||||
import brut.util.OS;
|
import brut.util.OS;
|
||||||
|
import brut.util.OSDetection;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@ -51,7 +54,7 @@ public class MissingDiv9PatchTest extends BaseTest {
|
|||||||
InputStream inputStream = getFileInputStream();
|
InputStream inputStream = getFileInputStream();
|
||||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
|
||||||
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
|
ResStreamDecoder decoder = OSDetection.isAndroid() ? new Res9patchAndroidStreamDecoder() : new Res9patchStreamDecoder();
|
||||||
decoder.decode(inputStream, outputStream);
|
decoder.decode(inputStream, outputStream);
|
||||||
|
|
||||||
BufferedImage image = ImageIO.read(new ByteArrayInputStream(outputStream.toByteArray()));
|
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")));
|
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() {
|
public static boolean is64Bit() {
|
||||||
if (isWindows()) {
|
if (isWindows()) {
|
||||||
String arch = System.getenv("PROCESSOR_ARCHITECTURE");
|
String arch = System.getenv("PROCESSOR_ARCHITECTURE");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user