Use AXML instead of Apkparser

This commit is contained in:
LoveSy 2020-12-20 15:36:27 +08:00 committed by kotori0
parent 76f438f959
commit 43c4c92365
31 changed files with 2805 additions and 26 deletions

3
.gitmodules vendored
View File

@ -1,6 +1,3 @@
[submodule "edxp-core/src/main/cpp/external/Dobby"]
path = edxp-core/src/main/cpp/external/Dobby
url = https://github.com/jmpews/Dobby.git
[submodule "apk-parser"]
path = apk-parser
url = https://github.com/jaredrummler/APKParser.git

View File

@ -25,7 +25,6 @@ dependencies {
api project(':xposed-bridge')
compileOnly project(':dexmaker')
compileOnly 'com.android.support:support-annotations:28.0.0'
implementation project(':apk-parser:library')
}

View File

@ -12,11 +12,12 @@ import android.content.res.XResources;
import com.elderdrivers.riru.edxp.config.ConfigManager;
import com.elderdrivers.riru.edxp.util.Hookers;
import com.elderdrivers.riru.edxp.util.MetaDataReader;
import com.elderdrivers.riru.edxp.util.Utils;
import com.jaredrummler.apkparser.ApkParser;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
@ -61,11 +62,12 @@ public class HandleBindApp extends XC_MethodHook {
boolean isModule = false;
int xposedminversion = -1;
boolean xposedsharedprefs = false;
try (ApkParser ap = ApkParser.create(new File(appInfo.sourceDir))){
isModule = ap.getApkMeta().metaData.containsKey("xposedmodule");
try {
Map<String, Object> metaData = MetaDataReader.getMetaData(new File(appInfo.sourceDir));
isModule = metaData.containsKey("xposedmodule");
if (isModule) {
xposedminversion = Integer.parseInt(ap.getApkMeta().metaData.get("xposedminversion"));
xposedsharedprefs = ap.getApkMeta().metaData.containsKey("xposedsharedprefs");
xposedminversion = (Integer) metaData.get("xposedminversion");
xposedsharedprefs = metaData.containsKey("xposedsharedprefs");
}
} catch (NumberFormatException | IOException e) {
Hookers.logE("ApkParser fails", e);

View File

@ -1 +1 @@
include ':edxp-core', ':xposed-bridge', ':hiddenapi-stubs', ':dexmaker', ':dalvikdx', ':edxp-common', ':edxp-yahfa', ':edxp-sandhook', ':edxp-service', ':apk-parser:library', ':sandhook-hooklib', ':sandhook-annotation'
include ':edxp-core', ':xposed-bridge', ':hiddenapi-stubs', ':dexmaker', ':dalvikdx', ':edxp-common', ':edxp-yahfa', ':edxp-sandhook', ':edxp-service', ':sandhook-hooklib', ':sandhook-annotation'

View File

@ -43,7 +43,6 @@ preBuild.doLast {
}
dependencies {
compileOnly project(':apk-parser:library')
compileOnly files(project(":dexmaker").tasks.getByName("makeJarRelease").outputs)
compileOnly files(project(":hiddenapi-stubs").tasks.getByName("makeStubJar").outputs)
}

View File

@ -0,0 +1,107 @@
package com.elderdrivers.riru.edxp.util;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.JarFile;
import pxb.android.axml.AxmlReader;
import pxb.android.axml.AxmlVisitor;
import pxb.android.axml.NodeVisitor;
public class MetaDataReader {
private final HashMap<String, Object> metaData = new HashMap<>();
public static Map<String, Object> getMetaData(File apk) throws IOException {
return new MetaDataReader(apk).metaData;
}
private MetaDataReader(File apk) throws IOException {
JarFile zip = new JarFile(apk);
InputStream is = zip.getInputStream(zip.getEntry("AndroidManifest.xml"));
byte[] bytes = getBytesFromInputStream(is);
AxmlReader reader = new AxmlReader(bytes);
reader.accept(new AxmlVisitor() {
@Override
public NodeVisitor child(String ns, String name) {
NodeVisitor child = super.child(ns, name);
return new ManifestTagVisitor(child);
}
});
}
public static byte[] getBytesFromInputStream(InputStream inputStream) throws IOException {
try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
byte[] b = new byte[1024];
int n;
while ((n = inputStream.read(b)) != -1) {
bos.write(b, 0, n);
}
byte[] data = bos.toByteArray();
return data;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private class ManifestTagVisitor extends NodeVisitor {
public ManifestTagVisitor(NodeVisitor child) {
super(child);
}
@Override
public NodeVisitor child(String ns, String name) {
NodeVisitor child = super.child(ns, name);
if ("application".equals(name)) {
return new ApplicationTagVisitor(child);
}
return child;
}
private class ApplicationTagVisitor extends NodeVisitor {
public ApplicationTagVisitor(NodeVisitor child) {
super(child);
}
@Override
public NodeVisitor child(String ns, String name) {
NodeVisitor child = super.child(ns, name);
if("meta-data".equals(name)) {
return new MetaDataVisitor(child);
}
return child;
}
}
}
private class MetaDataVisitor extends NodeVisitor {
public String name = null;
public Object value = null;
public MetaDataVisitor(NodeVisitor child) {
super(child);
}
@Override
public void attr(String ns, String name, int resourceId, int type, Object obj) {
if (type == 3 && "name".equals(name)) {
this.name = (String)obj;
}
if ("value".equals(name) ) {
value = obj;
}
super.attr(ns, name, resourceId, type, obj);
}
@Override
public void end() {
if(name != null && value != null) {
metaData.put(name, value);
}
super.end();
}
}
}

View File

@ -1,17 +1,11 @@
package de.robv.android.xposed;
import com.elderdrivers.riru.edxp.config.EdXpConfigGlobal;
import com.jaredrummler.apkparser.utils.Utils;
import java.lang.reflect.Member;
import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import static de.robv.android.xposed.XposedBridge.hookMethodNative;
import static de.robv.android.xposed.XposedBridge.log;
public final class PendingHooks {

View File

@ -1,17 +1,14 @@
package de.robv.android.xposed;
import android.annotation.SuppressLint;
import android.app.ActivityThread;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.preference.PreferenceManager;
import android.util.Log;
import com.android.internal.util.XmlUtils;
import com.jaredrummler.apkparser.ApkParser;
import com.elderdrivers.riru.edxp.util.MetaDataReader;
import org.xmlpull.v1.XmlPullParserException;
@ -76,11 +73,12 @@ public final class XSharedPreferences implements SharedPreferences {
boolean isModule = false;
int xposedminversion = -1;
boolean xposedsharedprefs = false;
try (ApkParser ap = ApkParser.create(new File(m))) {
isModule = ap.getApkMeta().metaData.containsKey("xposedmodule");
try {
Map<String, Object> metaData = MetaDataReader.getMetaData(new File(m));
isModule = metaData.containsKey("xposedmodule");
if (isModule) {
xposedminversion = Integer.parseInt(ap.getApkMeta().metaData.get("xposedminversion"));
xposedsharedprefs = ap.getApkMeta().metaData.containsKey("xposedsharedprefs");
xposedminversion = (Integer) metaData.get("xposedminversion");
xposedsharedprefs = metaData.containsKey("xposedsharedprefs");
}
} catch (NumberFormatException | IOException e) {
Log.w(TAG, "Apk parser fails: " + e);

View File

@ -0,0 +1,33 @@
/*
* Copyright (c) 2009-2013 Panxiaobo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package pxb.android;
public interface ResConst {
int RES_STRING_POOL_TYPE = 0x0001;
int RES_TABLE_TYPE = 0x0002;
int RES_TABLE_PACKAGE_TYPE = 0x0200;
int RES_TABLE_TYPE_SPEC_TYPE = 0x0202;
int RES_TABLE_TYPE_TYPE = 0x0201;
int RES_XML_TYPE = 0x0003;
int RES_XML_RESOURCE_MAP_TYPE = 0x0180;
int RES_XML_END_NAMESPACE_TYPE = 0x0101;
int RES_XML_END_ELEMENT_TYPE = 0x0103;
int RES_XML_START_NAMESPACE_TYPE = 0x0100;
int RES_XML_START_ELEMENT_TYPE = 0x0102;
int RES_XML_CDATA_TYPE = 0x0104;
}

View File

@ -0,0 +1,61 @@
/*
* Copyright (c) 2009-2013 Panxiaobo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package pxb.android;
public class StringItem {
public String data;
public int dataOffset;
public int index;
public StringItem() {
super();
}
public StringItem(String data) {
super();
this.data = data;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
StringItem other = (StringItem) obj;
if (data == null) {
if (other.data != null)
return false;
} else if (!data.equals(other.data))
return false;
return true;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((data == null) ? 0 : data.hashCode());
return result;
}
public String toString() {
return String.format("S%04d %s", index, data);
}
}

View File

@ -0,0 +1,163 @@
/*
* Copyright (c) 2009-2013 Panxiaobo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package pxb.android;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
@SuppressWarnings("serial")
public class StringItems extends ArrayList<StringItem> {
private static final int UTF8_FLAG = 0x00000100;
public static String[] read(ByteBuffer in) throws IOException {
int trunkOffset = in.position() - 8;
int stringCount = in.getInt();
int styleOffsetCount = in.getInt();
int flags = in.getInt();
int stringDataOffset = in.getInt();
int stylesOffset = in.getInt();
int offsets[] = new int[stringCount];
String strings[] = new String[stringCount];
for (int i = 0; i < stringCount; i++) {
offsets[i] = in.getInt();
}
int base = trunkOffset + stringDataOffset;
for (int i = 0; i < offsets.length; i++) {
in.position(base + offsets[i]);
String s;
if (0 != (flags & UTF8_FLAG)) {
u8length(in); // ignored
int u8len = u8length(in);
int start = in.position();
int blength = u8len;
while (in.get(start + blength) != 0) {
blength++;
}
s = new String(in.array(), start, blength, "UTF-8");
} else {
int length = u16length(in);
s = new String(in.array(), in.position(), length * 2, "UTF-16LE");
}
strings[i] = s;
}
return strings;
}
static int u16length(ByteBuffer in) {
int length = in.getShort() & 0xFFFF;
if (length > 0x7FFF) {
length = ((length & 0x7FFF) << 8) | (in.getShort() & 0xFFFF);
}
return length;
}
static int u8length(ByteBuffer in) {
int len = in.get() & 0xFF;
if ((len & 0x80) != 0) {
len = ((len & 0x7F) << 8) | (in.get() & 0xFF);
}
return len;
}
byte[] stringData;
public int getSize() {
return 5 * 4 + this.size() * 4 + stringData.length + 0;// TODO
}
public void prepare() throws IOException {
for (StringItem s : this) {
if (s.data.length() > 0x7FFF) {
useUTF8 = false;
}
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int i = 0;
int offset = 0;
baos.reset();
Map<String, Integer> map = new HashMap<String, Integer>();
for (StringItem item : this) {
item.index = i++;
String stringData = item.data;
Integer of = map.get(stringData);
if (of != null) {
item.dataOffset = of;
} else {
item.dataOffset = offset;
map.put(stringData, offset);
if (useUTF8) {
int length = stringData.length();
byte[] data = stringData.getBytes("UTF-8");
int u8lenght = data.length;
if (length > 0x7F) {
offset++;
baos.write((length >> 8) | 0x80);
}
baos.write(length);
if (u8lenght > 0x7F) {
offset++;
baos.write((u8lenght >> 8) | 0x80);
}
baos.write(u8lenght);
baos.write(data);
baos.write(0);
offset += 3 + u8lenght;
} else {
int length = stringData.length();
byte[] data = stringData.getBytes("UTF-16LE");
if (length > 0x7FFF) {
int x = (length >> 16) | 0x8000;
baos.write(x);
baos.write(x >> 8);
offset += 2;
}
baos.write(length);
baos.write(length >> 8);
baos.write(data);
baos.write(0);
baos.write(0);
offset += 4 + data.length;
}
}
}
// TODO
stringData = baos.toByteArray();
}
private boolean useUTF8 = true;
public void write(ByteBuffer out) throws IOException {
out.putInt(this.size());
out.putInt(0);// TODO style count
out.putInt(useUTF8 ? UTF8_FLAG : 0);
out.putInt(7 * 4 + this.size() * 4);
out.putInt(0);
for (StringItem item : this) {
out.putInt(item.dataOffset);
}
out.put(stringData);
// TODO
}
}

View File

@ -0,0 +1,73 @@
/*
* Copyright (c) 2009-2013 Panxiaobo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package pxb.android.arsc;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import pxb.android.axml.Util;
/**
* dump an arsc file
*
* @author bob
*
*/
public class ArscDumper {
public static void dump(List<Pkg> pkgs) {
for (int x = 0; x < pkgs.size(); x++) {
Pkg pkg = pkgs.get(x);
System.out.println(String.format(" Package %d id=%d name=%s typeCount=%d", x, pkg.id, pkg.name,
pkg.types.size()));
for (Type type : pkg.types.values()) {
System.out.println(String.format(" type %d %s", type.id - 1, type.name));
int resPrefix = pkg.id << 24 | type.id << 16;
for (int i = 0; i < type.specs.length; i++) {
ResSpec spec = type.getSpec(i);
System.out.println(String.format(" spec 0x%08x 0x%08x %s", resPrefix | spec.id, spec.flags,
spec.name));
}
for (int i = 0; i < type.configs.size(); i++) {
Config config = type.configs.get(i);
System.out.println(" config");
List<ResEntry> entries = new ArrayList<ResEntry>(config.resources.values());
for (int j = 0; j < entries.size(); j++) {
ResEntry entry = entries.get(j);
System.out.println(String.format(" resource 0x%08x %-20s: %s",
resPrefix | entry.spec.id, entry.spec.name, entry.value));
}
}
}
}
}
public static void main(String... args) throws IOException {
if (args.length == 0) {
System.err.println("asrc-dump file.arsc");
return;
}
byte[] data = Util.readFile(new File(args[0]));
List<Pkg> pkgs = new ArscParser(data).parse();
dump(pkgs);
}
}

View File

@ -0,0 +1,317 @@
/*
* Copyright (c) 2009-2013 Panxiaobo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package pxb.android.arsc;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import pxb.android.ResConst;
import pxb.android.StringItems;
/**
*
* Read the resources.arsc inside an Android apk.
*
* Usage:
*
* <pre>
* byte[] oldArscFile= ... ; //
* List&lt;Pkg&gt; pkgs = new ArscParser(oldArscFile).parse(); // read the file
* modify(pkgs); // do what you want here
* byte[] newArscFile = new ArscWriter(pkgs).toByteArray(); // build a new file
* </pre>
*
* The format of arsc is described here (gingerbread)
* <ul>
* <li>frameworks/base/libs/utils/ResourceTypes.cpp</li>
* <li>frameworks/base/include/utils/ResourceTypes.h</li>
* </ul>
* and the cmd line <code>aapt d resources abc.apk</code> is also good for debug
* (available in android sdk)
*
* <p>
* Todos:
* <ul>
* TODO add support to read styled strings
* </ul>
*
* <p>
* Thanks to the the following projects
* <ul>
* <li>android4me https://code.google.com/p/android4me/</li>
* <li>Apktool https://code.google.com/p/android-apktool</li>
* <li>Android http://source.android.com/</li>
* </ul>
*
* @author bob
*
*/
public class ArscParser implements ResConst {
/* pkg */class Chunk {
public final int headSize;
public final int location;
public final int size;
public final int type;
public Chunk() {
location = in.position();
type = in.getShort() & 0xFFFF;
headSize = in.getShort() & 0xFFFF;
size = in.getInt();
}
}
/**
* If set, this resource has been declared public, so libraries are allowed
* to reference it.
*/
static final int ENGRY_FLAG_PUBLIC = 0x0002;
/**
* If set, this is a complex entry, holding a set of name/value mappings. It
* is followed by an array of ResTable_map structures.
*/
final static short ENTRY_FLAG_COMPLEX = 0x0001;
public static final int TYPE_STRING = 0x03;
private int fileSize = -1;
private ByteBuffer in;
private String[] keyNamesX;
private Pkg pkg;
private List<Pkg> pkgs = new ArrayList<Pkg>();
private String[] strings;
private String[] typeNamesX;
public ArscParser(byte[] b) {
this.in = ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN);
}
public List<Pkg> parse() throws IOException {
if (fileSize < 0) {
Chunk head = new Chunk();
if (head.type != RES_TABLE_TYPE) {
throw new RuntimeException();
}
fileSize = head.size;
in.getInt();// packagecount
}
while (in.hasRemaining()) {
Chunk chunk = new Chunk();
switch (chunk.type) {
case RES_STRING_POOL_TYPE:
strings = StringItems.read(in);
break;
case RES_TABLE_PACKAGE_TYPE:
readPackage(in);
}
in.position(chunk.location + chunk.size);
}
return pkgs;
}
// private void readConfigFlags() {
// int size = in.getInt();
// if (size < 28) {
// throw new RuntimeException();
// }
// short mcc = in.getShort();
// short mnc = in.getShort();
//
// char[] language = new char[] { (char) in.get(), (char) in.get() };
// char[] country = new char[] { (char) in.get(), (char) in.get() };
//
// byte orientation = in.get();
// byte touchscreen = in.get();
// short density = in.getShort();
//
// byte keyboard = in.get();
// byte navigation = in.get();
// byte inputFlags = in.get();
// byte inputPad0 = in.get();
//
// short screenWidth = in.getShort();
// short screenHeight = in.getShort();
//
// short sdkVersion = in.getShort();
// short minorVersion = in.getShort();
//
// byte screenLayout = 0;
// byte uiMode = 0;
// short smallestScreenWidthDp = 0;
// if (size >= 32) {
// screenLayout = in.get();
// uiMode = in.get();
// smallestScreenWidthDp = in.getShort();
// }
//
// short screenWidthDp = 0;
// short screenHeightDp = 0;
//
// if (size >= 36) {
// screenWidthDp = in.getShort();
// screenHeightDp = in.getShort();
// }
//
// short layoutDirection = 0;
// if (size >= 38 && sdkVersion >= 17) {
// layoutDirection = in.getShort();
// }
//
// }
private void readEntry(Config config, ResSpec spec) {
int size = in.getShort();
int flags = in.getShort(); // ENTRY_FLAG_PUBLIC
int keyStr = in.getInt();
spec.updateName(keyNamesX[keyStr]);
ResEntry resEntry = new ResEntry(flags, spec);
if (0 != (flags & ENTRY_FLAG_COMPLEX)) {
int parent = in.getInt();
int count = in.getInt();
BagValue bag = new BagValue(parent);
for (int i = 0; i < count; i++) {
Map.Entry<Integer, Value> entry = new AbstractMap.SimpleEntry(in.getInt(), readValue());
bag.map.add(entry);
}
resEntry.value = bag;
} else {
resEntry.value = readValue();
}
config.resources.put(spec.id, resEntry);
}
private void readPackage(ByteBuffer in) throws IOException {
int pid = in.getInt() % 0xFF;
String name;
{
int nextPisition = in.position() + 128 * 2;
StringBuilder sb = new StringBuilder(32);
for (int i = 0; i < 128; i++) {
int s = in.getShort();
if (s == 0) {
break;
} else {
sb.append((char) s);
}
}
name = sb.toString();
in.position(nextPisition);
}
pkg = new Pkg(pid, name);
pkgs.add(pkg);
int typeStringOff = in.getInt();
int typeNameCount = in.getInt();
int keyStringOff = in.getInt();
int specNameCount = in.getInt();
{
Chunk chunk = new Chunk();
if (chunk.type != RES_STRING_POOL_TYPE) {
throw new RuntimeException();
}
typeNamesX = StringItems.read(in);
in.position(chunk.location + chunk.size);
}
{
Chunk chunk = new Chunk();
if (chunk.type != RES_STRING_POOL_TYPE) {
throw new RuntimeException();
}
keyNamesX = StringItems.read(in);
in.position(chunk.location + chunk.size);
}
out: while (in.hasRemaining()) {
Chunk chunk = new Chunk();
switch (chunk.type) {
case RES_TABLE_TYPE_SPEC_TYPE: {
int tid = in.get() & 0xFF;
in.get(); // res0
in.getShort();// res1
int entryCount = in.getInt();
Type t = pkg.getType(tid, typeNamesX[tid - 1], entryCount);
for (int i = 0; i < entryCount; i++) {
t.getSpec(i).flags = in.getInt();
}
}
break;
case RES_TABLE_TYPE_TYPE: {
int tid = in.get() & 0xFF;
in.get(); // res0
in.getShort();// res1
int entryCount = in.getInt();
Type t = pkg.getType(tid, typeNamesX[tid - 1], entryCount);
int entriesStart = in.getInt();
int p = in.position();
int size = in.getInt();
// readConfigFlags();
byte[] data = new byte[size];
in.position(p);
in.get(data);
Config config = new Config(data, entryCount);
in.position(chunk.location + chunk.headSize);
int[] entrys = new int[entryCount];
for (int i = 0; i < entryCount; i++) {
entrys[i] = in.getInt();
}
for (int i = 0; i < entrys.length; i++) {
if (entrys[i] != -1) {
in.position(chunk.location + entriesStart + entrys[i]);
ResSpec spec = t.getSpec(i);
readEntry(config, spec);
}
}
t.addConfig(config);
}
break;
default:
break out;
}
in.position(chunk.location + chunk.size);
}
}
private Object readValue() {
int size1 = in.getShort();// 8
int zero = in.get();// 0
int type = in.get() & 0xFF; // TypedValue.*
int data = in.getInt();
String raw = null;
if (type == TYPE_STRING) {
raw = strings[data];
}
return new Value(type, data, raw);
}
}

View File

@ -0,0 +1,400 @@
/*
* Copyright (c) 2009-2013 Panxiaobo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package pxb.android.arsc;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import pxb.android.ResConst;
import pxb.android.StringItem;
import pxb.android.StringItems;
import pxb.android.axml.Util;
/**
* Write pkgs to an arsc file
*
* @see ArscParser
* @author bob
*
*/
public class ArscWriter implements ResConst {
private static class PkgCtx {
Map<String, StringItem> keyNames = new HashMap<String, StringItem>();
StringItems keyNames0 = new StringItems();
public int keyStringOff;
int offset;
Pkg pkg;
int pkgSize;
List<StringItem> typeNames = new ArrayList<StringItem>();
StringItems typeNames0 = new StringItems();
int typeStringOff;
public void addKeyName(String name) {
if (keyNames.containsKey(name)) {
return;
}
StringItem stringItem = new StringItem(name);
keyNames.put(name, stringItem);
keyNames0.add(stringItem);
}
public void addTypeName(int id, String name) {
while (typeNames.size() <= id) {
typeNames.add(null);
}
StringItem item = typeNames.get(id);
if (item == null) {
typeNames.set(id, new StringItem(name));
} else {
throw new RuntimeException();
}
}
}
private static void D(String fmt, Object... args) {
}
private List<PkgCtx> ctxs = new ArrayList<PkgCtx>(5);
private List<Pkg> pkgs;
private Map<String, StringItem> strTable = new TreeMap<String, StringItem>();
private StringItems strTable0 = new StringItems();
public ArscWriter(List<Pkg> pkgs) {
this.pkgs = pkgs;
}
public static void main(String... args) throws IOException {
if (args.length < 2) {
System.err.println("asrc-write-test in.arsc out.arsc");
return;
}
byte[] data = Util.readFile(new File(args[0]));
List<Pkg> pkgs = new ArscParser(data).parse();
// ArscDumper.dump(pkgs);
byte[] data2 = new ArscWriter(pkgs).toByteArray();
// ArscDumper.dump(new ArscParser(data2).parse());
Util.writeFile(data2, new File(args[1]));
}
private void addString(String str) {
if (strTable.containsKey(str)) {
return;
}
StringItem stringItem = new StringItem(str);
strTable.put(str, stringItem);
strTable0.add(stringItem);
}
private int count() {
int size = 0;
size += 8 + 4;// chunk, pkgcount
{
int stringSize = strTable0.getSize();
if (stringSize % 4 != 0) {
stringSize += 4 - stringSize % 4;
}
size += 8 + stringSize;// global strings
}
for (PkgCtx ctx : ctxs) {
ctx.offset = size;
int pkgSize = 0;
pkgSize += 8 + 4 + 256;// chunk,pid+name
pkgSize += 4 * 4;
ctx.typeStringOff = pkgSize;
{
int stringSize = ctx.typeNames0.getSize();
if (stringSize % 4 != 0) {
stringSize += 4 - stringSize % 4;
}
pkgSize += 8 + stringSize;// type names
}
ctx.keyStringOff = pkgSize;
{
int stringSize = ctx.keyNames0.getSize();
if (stringSize % 4 != 0) {
stringSize += 4 - stringSize % 4;
}
pkgSize += 8 + stringSize;// key names
}
for (Type type : ctx.pkg.types.values()) {
type.wPosition = size + pkgSize;
pkgSize += 8 + 4 + 4 + 4 * type.specs.length; // trunk,id,entryCount,
// configs
for (Config config : type.configs) {
config.wPosition = pkgSize + size;
int configBasePostion = pkgSize;
pkgSize += 8 + 4 + 4 + 4; // trunk,id,entryCount,entriesStart
int size0 = config.id.length;
if (size0 % 4 != 0) {
size0 += 4 - size0 % 4;
}
pkgSize += size0;// config
if (pkgSize - configBasePostion > 0x0038) {
throw new RuntimeException("config id too big");
} else {
pkgSize = configBasePostion + 0x0038;
}
pkgSize += 4 * config.entryCount;// offset
config.wEntryStart = pkgSize - configBasePostion;
int entryBase = pkgSize;
for (ResEntry e : config.resources.values()) {
e.wOffset = pkgSize - entryBase;
pkgSize += 8;// size,flag,keyString
if (e.value instanceof BagValue) {
BagValue big = (BagValue) e.value;
pkgSize += 8 + big.map.size() * 12;
} else {
pkgSize += 8;
}
}
config.wChunkSize = pkgSize - configBasePostion;
}
}
ctx.pkgSize = pkgSize;
size += pkgSize;
}
return size;
}
private List<PkgCtx> prepare() throws IOException {
for (Pkg pkg : pkgs) {
PkgCtx ctx = new PkgCtx();
ctx.pkg = pkg;
ctxs.add(ctx);
for (Type type : pkg.types.values()) {
ctx.addTypeName(type.id - 1, type.name);
for (ResSpec spec : type.specs) {
ctx.addKeyName(spec.name);
}
for (Config config : type.configs) {
for (ResEntry e : config.resources.values()) {
Object object = e.value;
if (object instanceof BagValue) {
travelBagValue((BagValue) object);
} else {
travelValue((Value) object);
}
}
}
}
ctx.keyNames0.prepare();
ctx.typeNames0.addAll(ctx.typeNames);
ctx.typeNames0.prepare();
}
strTable0.prepare();
return ctxs;
}
public byte[] toByteArray() throws IOException {
prepare();
int size = count();
ByteBuffer out = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN);
write(out, size);
return out.array();
}
private void travelBagValue(BagValue bag) {
for (Map.Entry<Integer, Value> e : bag.map) {
travelValue(e.getValue());
}
}
private void travelValue(Value v) {
if (v.raw != null) {
addString(v.raw);
}
}
private void write(ByteBuffer out, int size) throws IOException {
out.putInt(RES_TABLE_TYPE | (0x000c << 16));
out.putInt(size);
out.putInt(ctxs.size());
{
int stringSize = strTable0.getSize();
int padding = 0;
if (stringSize % 4 != 0) {
padding = 4 - stringSize % 4;
}
out.putInt(RES_STRING_POOL_TYPE | (0x001C << 16));
out.putInt(stringSize + padding + 8);
strTable0.write(out);
out.put(new byte[padding]);
}
for (PkgCtx pctx : ctxs) {
if (out.position() != pctx.offset) {
throw new RuntimeException();
}
final int basePosition = out.position();
out.putInt(RES_TABLE_PACKAGE_TYPE | (0x011c << 16));
out.putInt(pctx.pkgSize);
out.putInt(pctx.pkg.id);
int p = out.position();
out.put(pctx.pkg.name.getBytes("UTF-16LE"));
out.position(p + 256);
out.putInt(pctx.typeStringOff);
out.putInt(pctx.typeNames0.size());
out.putInt(pctx.keyStringOff);
out.putInt(pctx.keyNames0.size());
{
if (out.position() - basePosition != pctx.typeStringOff) {
throw new RuntimeException();
}
int stringSize = pctx.typeNames0.getSize();
int padding = 0;
if (stringSize % 4 != 0) {
padding = 4 - stringSize % 4;
}
out.putInt(RES_STRING_POOL_TYPE | (0x001C << 16));
out.putInt(stringSize + padding + 8);
pctx.typeNames0.write(out);
out.put(new byte[padding]);
}
{
if (out.position() - basePosition != pctx.keyStringOff) {
throw new RuntimeException();
}
int stringSize = pctx.keyNames0.getSize();
int padding = 0;
if (stringSize % 4 != 0) {
padding = 4 - stringSize % 4;
}
out.putInt(RES_STRING_POOL_TYPE | (0x001C << 16));
out.putInt(stringSize + padding + 8);
pctx.keyNames0.write(out);
out.put(new byte[padding]);
}
for (Type t : pctx.pkg.types.values()) {
D("[%08x]write spec", out.position(), t.name);
if (t.wPosition != out.position()) {
throw new RuntimeException();
}
out.putInt(RES_TABLE_TYPE_SPEC_TYPE | (0x0010 << 16));
out.putInt(4 * 4 + 4 * t.specs.length);// size
out.putInt(t.id);
out.putInt(t.specs.length);
for (ResSpec spec : t.specs) {
out.putInt(spec.flags);
}
for (Config config : t.configs) {
D("[%08x]write config", out.position());
int typeConfigPosition = out.position();
if (config.wPosition != typeConfigPosition) {
throw new RuntimeException();
}
out.putInt(RES_TABLE_TYPE_TYPE | (0x0038 << 16));
out.putInt(config.wChunkSize);// size
out.putInt(t.id);
out.putInt(t.specs.length);
out.putInt(config.wEntryStart);
D("[%08x]write config ids", out.position());
out.put(config.id);
int size0 = config.id.length;
int padding = 0;
if (size0 % 4 != 0) {
padding = 4 - size0 % 4;
}
out.put(new byte[padding]);
out.position(typeConfigPosition + 0x0038);
D("[%08x]write config entry offsets", out.position());
for (int i = 0; i < config.entryCount; i++) {
ResEntry entry = config.resources.get(i);
if (entry == null) {
out.putInt(-1);
} else {
out.putInt(entry.wOffset);
}
}
if (out.position() - typeConfigPosition != config.wEntryStart) {
throw new RuntimeException();
}
D("[%08x]write config entrys", out.position());
for (ResEntry e : config.resources.values()) {
D("[%08x]ResTable_entry", out.position());
boolean isBag = e.value instanceof BagValue;
out.putShort((short) (isBag ? 16 : 8));
int flag = e.flag;
if (isBag) { // add complex flag
flag |= ArscParser.ENTRY_FLAG_COMPLEX;
} else { // remove
flag &= ~ArscParser.ENTRY_FLAG_COMPLEX;
}
out.putShort((short) flag);
out.putInt(pctx.keyNames.get(e.spec.name).index);
if (isBag) {
BagValue bag = (BagValue) e.value;
out.putInt(bag.parent);
out.putInt(bag.map.size());
for (Map.Entry<Integer, Value> entry : bag.map) {
out.putInt(entry.getKey());
writeValue(entry.getValue(), out);
}
} else {
writeValue((Value) e.value, out);
}
}
}
}
}
}
private void writeValue(Value value, ByteBuffer out) {
out.putShort((short) 8);
out.put((byte) 0);
out.put((byte) value.type);
if (value.type == ArscParser.TYPE_STRING) {
out.putInt(strTable.get(value.raw).index);
} else {
out.putInt(value.data);
}
}
}

View File

@ -0,0 +1,70 @@
/*
* Copyright (c) 2009-2013 Panxiaobo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package pxb.android.arsc;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
public class BagValue {
public List<Map.Entry<Integer, Value>> map = new ArrayList<Entry<Integer, Value>>();
public final int parent;
public BagValue(int parent) {
this.parent = parent;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof BagValue))
return false;
BagValue other = (BagValue) obj;
if (map == null) {
if (other.map != null)
return false;
} else if (!map.equals(other.map))
return false;
if (parent != other.parent)
return false;
return true;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((map == null) ? 0 : map.hashCode());
result = prime * result + parent;
return result;
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(String.format("{bag%08x", parent));
for (Map.Entry<Integer, Value> e : map) {
sb.append(",").append(String.format("0x%08x", e.getKey()));
sb.append("=");
sb.append(e.getValue());
}
return sb.append("}").toString();
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) 2009-2013 Panxiaobo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package pxb.android.arsc;
import java.util.Map;
import java.util.TreeMap;
public class Config {
public final int entryCount;
public final byte[] id;
public Map<Integer, ResEntry> resources = new TreeMap<Integer, ResEntry>();
/* package */int wChunkSize;
/* package */int wEntryStart;
/* package */int wPosition;
public Config(byte[] id, int entryCount) {
super();
this.id = id;
this.entryCount = entryCount;
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright (c) 2009-2013 Panxiaobo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package pxb.android.arsc;
import java.util.TreeMap;
public class Pkg {
public final int id;
public String name;
public TreeMap<Integer, Type> types = new TreeMap<Integer, Type>();
public Pkg(int id, String name) {
super();
this.id = id;
this.name = name;
}
public Type getType(int tid, String name, int entrySize) {
Type type = types.get(tid);
if (type != null) {
if (name != null) {
if (type.name == null) {
type.name = name;
} else if (!name.endsWith(type.name)) {
throw new RuntimeException();
}
if (type.specs.length != entrySize) {
throw new RuntimeException();
}
}
} else {
type = new Type();
type.id = tid;
type.name = name;
type.specs = new ResSpec[entrySize];
types.put(tid, type);
}
return type;
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright (c) 2009-2013 Panxiaobo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package pxb.android.arsc;
public class ResEntry {
public final int flag;
public final ResSpec spec;
/**
* {@link BagValue} or {@link Value}
*/
public Object value;
/* package */int wOffset;
public ResEntry(int flag, ResSpec spec) {
super();
this.flag = flag;
this.spec = spec;
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright (c) 2009-2013 Panxiaobo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package pxb.android.arsc;
public class ResSpec {
public int flags;
public final int id;
public String name;
public ResSpec(int id) {
super();
this.id = id;
}
public void updateName(String s) {
String name = this.name;
if (name == null) {
this.name = s;
} else if (!s.equals(name)) {
throw new RuntimeException();
}
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (c) 2009-2013 Panxiaobo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package pxb.android.arsc;
import java.util.ArrayList;
import java.util.List;
public class Type {
public List<Config> configs = new ArrayList<Config>();
public int id;
public String name;
public ResSpec[] specs;
/* package */int wPosition;
public void addConfig(Config config) {
if (config.entryCount != specs.length) {
throw new RuntimeException();
}
configs.add(config);
}
public ResSpec getSpec(int resId) {
ResSpec res = specs[resId];
if (res == null) {
res = new ResSpec(resId);
specs[resId] = res;
}
return res;
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright (c) 2009-2013 Panxiaobo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package pxb.android.arsc;
public class Value {
public final int data;
public String raw;
public final int type;
public Value(int type, int data, String raw) {
super();
this.type = type;
this.data = data;
this.raw = raw;
}
public String toString() {
if (type == 0x03) {
return raw;
}
return String.format("{t=0x%02x d=0x%08x}", type, data);
}
}

View File

@ -0,0 +1,142 @@
/*
* Copyright (c) 2009-2013 Panxiaobo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package pxb.android.axml;
import java.util.ArrayList;
import java.util.List;
public class Axml extends AxmlVisitor {
public static class Node extends NodeVisitor {
public static class Attr {
public String ns, name;
public int resourceId, type;
public Object value;
public void accept(NodeVisitor nodeVisitor) {
nodeVisitor.attr(ns, name, resourceId, type, value);
}
}
public static class Text {
public int ln;
public String text;
public void accept(NodeVisitor nodeVisitor) {
nodeVisitor.text(ln, text);
}
}
public List<Attr> attrs = new ArrayList<Attr>();
public List<Node> children = new ArrayList<Node>();
public Integer ln;
public String ns, name;
public Text text;
public void accept(NodeVisitor nodeVisitor) {
NodeVisitor nodeVisitor2 = nodeVisitor.child(ns, name);
acceptB(nodeVisitor2);
nodeVisitor2.end();
}
public void acceptB(NodeVisitor nodeVisitor) {
if (text != null) {
text.accept(nodeVisitor);
}
for (Attr a : attrs) {
a.accept(nodeVisitor);
}
if (ln != null) {
nodeVisitor.line(ln);
}
for (Node c : children) {
c.accept(nodeVisitor);
}
}
@Override
public void attr(String ns, String name, int resourceId, int type, Object obj) {
Attr attr = new Attr();
attr.name = name;
attr.ns = ns;
attr.resourceId = resourceId;
attr.type = type;
attr.value = obj;
attrs.add(attr);
}
@Override
public NodeVisitor child(String ns, String name) {
Node node = new Node();
node.name = name;
node.ns = ns;
children.add(node);
return node;
}
@Override
public void line(int ln) {
this.ln = ln;
}
@Override
public void text(int lineNumber, String value) {
Text text = new Text();
text.ln = lineNumber;
text.text = value;
this.text = text;
}
}
public static class Ns {
public int ln;
public String prefix, uri;
public void accept(AxmlVisitor visitor) {
visitor.ns(prefix, uri, ln);
}
}
public List<Node> firsts = new ArrayList<Node>();
public List<Ns> nses = new ArrayList<Ns>();
public void accept(final AxmlVisitor visitor) {
for (Ns ns : nses) {
ns.accept(visitor);
}
for (Node first : firsts) {
first.accept(visitor);
}
}
@Override
public NodeVisitor child(String ns, String name) {
Node node = new Node();
node.name = name;
node.ns = ns;
firsts.add(node);
return node;
}
@Override
public void ns(String prefix, String uri, int ln) {
Ns ns = new Ns();
ns.prefix = prefix;
ns.uri = uri;
ns.ln = ln;
nses.add(ns);
}
}

View File

@ -0,0 +1,274 @@
/*
* Copyright (c) 2009-2013 Panxiaobo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package pxb.android.axml;
import static pxb.android.axml.NodeVisitor.TYPE_INT_BOOLEAN;
import static pxb.android.axml.NodeVisitor.TYPE_STRING;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import pxb.android.ResConst;
import pxb.android.StringItems;
/**
* a class to read android axml
*
* @author <a href="mailto:pxb1988@gmail.com">Panxiaobo</a>
*/
public class AxmlParser implements ResConst {
public static final int END_FILE = 7;
public static final int END_NS = 5;
public static final int END_TAG = 3;
public static final int START_FILE = 1;
public static final int START_NS = 4;
public static final int START_TAG = 2;
public static final int TEXT = 6;
// private int attrName[];
// private int attrNs[];
// private int attrResId[];
// private int attrType[];
// private Object attrValue[];
private int attributeCount;
private IntBuffer attrs;
private int classAttribute;
private int fileSize = -1;
private int idAttribute;
private ByteBuffer in;
private int lineNumber;
private int nameIdx;
private int nsIdx;
private int prefixIdx;
private int[] resourceIds;
private String[] strings;
private int styleAttribute;
private int textIdx;
public AxmlParser(byte[] data) {
this(ByteBuffer.wrap(data));
}
public AxmlParser(ByteBuffer in) {
super();
this.in = in.order(ByteOrder.LITTLE_ENDIAN);
}
public int getAttrCount() {
return attributeCount;
}
public int getAttributeCount() {
return attributeCount;
}
public String getAttrName(int i) {
int idx = attrs.get(i * 5 + 1);
return strings[idx];
}
public String getAttrNs(int i) {
int idx = attrs.get(i * 5 + 0);
return idx >= 0 ? strings[idx] : null;
}
String getAttrRawString(int i) {
int idx = attrs.get(i * 5 + 2);
if (idx >= 0) {
return strings[idx];
}
return null;
}
public int getAttrResId(int i) {
if (resourceIds != null) {
int idx = attrs.get(i * 5 + 1);
if (idx >= 0 && idx < resourceIds.length) {
return resourceIds[idx];
}
}
return -1;
}
public int getAttrType(int i) {
return attrs.get(i * 5 + 3) >> 24;
}
public Object getAttrValue(int i) {
int v = attrs.get(i * 5 + 4);
if (i == idAttribute) {
return ValueWrapper.wrapId(v, getAttrRawString(i));
} else if (i == styleAttribute) {
return ValueWrapper.wrapStyle(v, getAttrRawString(i));
} else if (i == classAttribute) {
return ValueWrapper.wrapClass(v, getAttrRawString(i));
}
switch (getAttrType(i)) {
case TYPE_STRING:
return strings[v];
case TYPE_INT_BOOLEAN:
return v != 0;
default:
return v;
}
}
public int getLineNumber() {
return lineNumber;
}
public String getName() {
return strings[nameIdx];
}
public String getNamespacePrefix() {
return strings[prefixIdx];
}
public String getNamespaceUri() {
return nsIdx >= 0 ? strings[nsIdx] : null;
}
public String getText() {
return strings[textIdx];
}
public int next() throws IOException {
if (fileSize < 0) {
int type = in.getInt() & 0xFFFF;
if (type != RES_XML_TYPE) {
throw new RuntimeException();
}
fileSize = in.getInt();
return START_FILE;
}
int event = -1;
for (int p = in.position(); p < fileSize; p = in.position()) {
int type = in.getInt() & 0xFFFF;
int size = in.getInt();
switch (type) {
case RES_XML_START_ELEMENT_TYPE: {
{
lineNumber = in.getInt();
in.getInt();/* skip, 0xFFFFFFFF */
nsIdx = in.getInt();
nameIdx = in.getInt();
int flag = in.getInt();// 0x00140014 ?
if (flag != 0x00140014) {
throw new RuntimeException();
}
}
attributeCount = in.getShort() & 0xFFFF;
idAttribute = (in.getShort() & 0xFFFF) - 1;
classAttribute = (in.getShort() & 0xFFFF) - 1;
styleAttribute = (in.getShort() & 0xFFFF) - 1;
attrs = in.asIntBuffer();
// attrResId = new int[attributeCount];
// attrName = new int[attributeCount];
// attrNs = new int[attributeCount];
// attrType = new int[attributeCount];
// attrValue = new Object[attributeCount];
// for (int i = 0; i < attributeCount; i++) {
// int attrNsIdx = in.getInt();
// int attrNameIdx = in.getInt();
// int raw = in.getInt();
// int aValueType = in.getInt() >>> 24;
// int aValue = in.getInt();
// Object value = null;
// switch (aValueType) {
// case TYPE_STRING:
// value = strings[aValue];
// break;
// case TYPE_INT_BOOLEAN:
// value = aValue != 0;
// break;
// default:
// value = aValue;
// }
// int resourceId = attrNameIdx < this.resourceIds.length ?
// resourceIds[attrNameIdx] : -1;
// attrNs[i] = attrNsIdx;
// attrName[i] = attrNameIdx;
// attrType[i] = aValueType;
// attrResId[i] = resourceId;
// attrValue[i] = value;
// }
event = START_TAG;
}
break;
case RES_XML_END_ELEMENT_TYPE: {
in.position(p + size);
event = END_TAG;
}
break;
case RES_XML_START_NAMESPACE_TYPE:
lineNumber = in.getInt();
in.getInt();/* 0xFFFFFFFF */
prefixIdx = in.getInt();
nsIdx = in.getInt();
event = START_NS;
break;
case RES_XML_END_NAMESPACE_TYPE:
in.position(p + size);
event = END_NS;
break;
case RES_STRING_POOL_TYPE:
strings = StringItems.read(in);
in.position(p + size);
continue;
case RES_XML_RESOURCE_MAP_TYPE:
int count = size / 4 - 2;
resourceIds = new int[count];
for (int i = 0; i < count; i++) {
resourceIds[i] = in.getInt();
}
in.position(p + size);
continue;
case RES_XML_CDATA_TYPE:
lineNumber = in.getInt();
in.getInt();/* 0xFFFFFFFF */
textIdx = in.getInt();
in.getInt();/* 00000008 00000000 */
in.getInt();
event = TEXT;
break;
default:
throw new RuntimeException("Unsupported type: " + type);
}
in.position(p + size);
return event;
}
return END_FILE;
}
}

View File

@ -0,0 +1,94 @@
/*
* Copyright (c) 2009-2013 Panxiaobo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package pxb.android.axml;
import static pxb.android.axml.AxmlParser.END_FILE;
import static pxb.android.axml.AxmlParser.END_NS;
import static pxb.android.axml.AxmlParser.END_TAG;
import static pxb.android.axml.AxmlParser.START_FILE;
import static pxb.android.axml.AxmlParser.START_NS;
import static pxb.android.axml.AxmlParser.START_TAG;
import static pxb.android.axml.AxmlParser.TEXT;
import java.io.IOException;
import java.util.Stack;
import de.robv.android.xposed.XposedBridge;
/**
* a class to read android axml
*
* @author <a href="mailto:pxb1988@gmail.com">Panxiaobo</a>
*/
public class AxmlReader {
public static final NodeVisitor EMPTY_VISITOR = new NodeVisitor() {
@Override
public NodeVisitor child(String ns, String name) {
return this;
}
};
final AxmlParser parser;
public AxmlReader(byte[] data) {
super();
this.parser = new AxmlParser(data);
}
public void accept(final AxmlVisitor av) throws IOException {
Stack<NodeVisitor> nvs = new Stack<NodeVisitor>();
NodeVisitor tos = av;
while (true) {
int type = parser.next();
switch (type) {
case START_FILE:
break;
case START_TAG:
nvs.push(tos);
tos = tos.child(parser.getNamespaceUri(), parser.getName());
if (tos != null) {
if (tos != EMPTY_VISITOR) {
tos.line(parser.getLineNumber());
for (int i = 0; i < parser.getAttrCount(); i++) {
tos.attr(parser.getAttrNs(i), parser.getAttrName(i), parser.getAttrResId(i),
parser.getAttrType(i), parser.getAttrValue(i));
}
}
} else {
tos = EMPTY_VISITOR;
}
break;
case END_TAG:
tos.end();
tos = nvs.pop();
break;
case START_NS:
av.ns(parser.getNamespacePrefix(), parser.getNamespaceUri(), parser.getLineNumber());
break;
case END_NS:
break;
case TEXT:
tos.text(parser.getLineNumber(), parser.getText());
break;
case END_FILE:
return;
default:
XposedBridge.log("Unsupported tag: " + type);
}
}
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright (c) 2009-2013 Panxiaobo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package pxb.android.axml;
/**
* visitor to visit an axml
*
* @author <a href="mailto:pxb1988@gmail.com">Panxiaobo</a>
*/
public class AxmlVisitor extends NodeVisitor {
public AxmlVisitor() {
super();
}
public AxmlVisitor(NodeVisitor av) {
super(av);
}
/**
* create a ns
*
* @param prefix
* @param uri
* @param ln
*/
public void ns(String prefix, String uri, int ln) {
if (nv != null && nv instanceof AxmlVisitor) {
((AxmlVisitor) nv).ns(prefix, uri, ln);
}
}
}

View File

@ -0,0 +1,442 @@
/*
* Copyright (c) 2009-2013 Panxiaobo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package pxb.android.axml;
import static pxb.android.ResConst.RES_STRING_POOL_TYPE;
import static pxb.android.ResConst.RES_XML_CDATA_TYPE;
import static pxb.android.ResConst.RES_XML_END_ELEMENT_TYPE;
import static pxb.android.ResConst.RES_XML_END_NAMESPACE_TYPE;
import static pxb.android.ResConst.RES_XML_RESOURCE_MAP_TYPE;
import static pxb.android.ResConst.RES_XML_START_ELEMENT_TYPE;
import static pxb.android.ResConst.RES_XML_START_NAMESPACE_TYPE;
import static pxb.android.ResConst.RES_XML_TYPE;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.TreeSet;
import pxb.android.StringItem;
import pxb.android.StringItems;
/**
* a class to write android axml
*
* @author <a href="mailto:pxb1988@gmail.com">Panxiaobo</a>
*/
public class AxmlWriter extends AxmlVisitor {
static final Comparator<Attr> ATTR_CMP = new Comparator<Attr>() {
@Override
public int compare(Attr a, Attr b) {
int x = a.resourceId - b.resourceId;
if (x == 0) {
x = a.name.data.compareTo(b.name.data);
if (x == 0) {
boolean aNsIsnull = a.ns == null;
boolean bNsIsnull = b.ns == null;
if (aNsIsnull) {
if (bNsIsnull) {
x = 0;
} else {
x = -1;
}
} else {
if (bNsIsnull) {
x = 1;
} else {
x = a.ns.data.compareTo(b.ns.data);
}
}
}
}
return x;
}
};
static class Attr {
public int index;
public StringItem name;
public StringItem ns;
public int resourceId;
public int type;
public Object value;
public StringItem raw;
public Attr(StringItem ns, StringItem name, int resourceId) {
super();
this.ns = ns;
this.name = name;
this.resourceId = resourceId;
}
public void prepare(AxmlWriter axmlWriter) {
ns = axmlWriter.updateNs(ns);
if (this.name != null) {
if (resourceId != -1) {
this.name = axmlWriter.updateWithResourceId(this.name, this.resourceId);
} else {
this.name = axmlWriter.update(this.name);
}
}
if (value instanceof StringItem) {
value = axmlWriter.update((StringItem) value);
}
if (raw != null) {
raw = axmlWriter.update(raw);
}
}
}
static class NodeImpl extends NodeVisitor {
private Set<Attr> attrs = new TreeSet<Attr>(ATTR_CMP);
private List<NodeImpl> children = new ArrayList<NodeImpl>();
private int line;
private StringItem name;
private StringItem ns;
private StringItem text;
private int textLineNumber;
Attr id;
Attr style;
Attr clz;
public NodeImpl(String ns, String name) {
super(null);
this.ns = ns == null ? null : new StringItem(ns);
this.name = name == null ? null : new StringItem(name);
}
@Override
public void attr(String ns, String name, int resourceId, int type, Object value) {
if (name == null) {
throw new RuntimeException("name can't be null");
}
Attr a = new Attr(ns == null ? null : new StringItem(ns), new StringItem(name), resourceId);
a.type = type;
if (value instanceof ValueWrapper) {
ValueWrapper valueWrapper = (ValueWrapper) value;
if (valueWrapper.raw != null) {
a.raw = new StringItem(valueWrapper.raw);
}
a.value = valueWrapper.ref;
switch (valueWrapper.type) {
case ValueWrapper.CLASS:
clz = a;
break;
case ValueWrapper.ID:
id = a;
break;
case ValueWrapper.STYLE:
style = a;
break;
}
} else if (type == TYPE_STRING) {
StringItem raw = new StringItem((String) value);
a.raw = raw;
a.value = raw;
} else {
a.raw = null;
a.value = value;
}
attrs.add(a);
}
@Override
public NodeVisitor child(String ns, String name) {
NodeImpl child = new NodeImpl(ns, name);
this.children.add(child);
return child;
}
@Override
public void end() {
}
@Override
public void line(int ln) {
this.line = ln;
}
public int prepare(AxmlWriter axmlWriter) {
ns = axmlWriter.updateNs(ns);
name = axmlWriter.update(name);
int attrIndex = 0;
for (Attr attr : attrs) {
attr.index = attrIndex++;
attr.prepare(axmlWriter);
}
text = axmlWriter.update(text);
int size = 24 + 36 + attrs.size() * 20;// 24 for end tag,36+x*20 for
// start tag
for (NodeImpl child : children) {
size += child.prepare(axmlWriter);
}
if (text != null) {
size += 28;
}
return size;
}
@Override
public void text(int ln, String value) {
this.text = new StringItem(value);
this.textLineNumber = ln;
}
void write(ByteBuffer out) throws IOException {
// start tag
out.putInt(RES_XML_START_ELEMENT_TYPE | (0x0010 << 16));
out.putInt(36 + attrs.size() * 20);
out.putInt(line);
out.putInt(0xFFFFFFFF);
out.putInt(ns != null ? this.ns.index : -1);
out.putInt(name.index);
out.putInt(0x00140014);// TODO
out.putShort((short) this.attrs.size());
out.putShort((short) (id == null ? 0 : id.index + 1));
out.putShort((short) (clz == null ? 0 : clz.index + 1));
out.putShort((short) (style == null ? 0 : style.index + 1));
for (Attr attr : attrs) {
out.putInt(attr.ns == null ? -1 : attr.ns.index);
out.putInt(attr.name.index);
out.putInt(attr.raw != null ? attr.raw.index : -1);
out.putInt((attr.type << 24) | 0x000008);
Object v = attr.value;
if (v instanceof StringItem) {
out.putInt(((StringItem) attr.value).index);
} else if (v instanceof Boolean) {
out.putInt(Boolean.TRUE.equals(v) ? -1 : 0);
} else if (v instanceof Float) {
out.putInt(Float.floatToIntBits((float) v));
} else {
out.putInt((Integer) attr.value);
}
}
if (this.text != null) {
out.putInt(RES_XML_CDATA_TYPE | (0x0010 << 16));
out.putInt(28);
out.putInt(textLineNumber);
out.putInt(0xFFFFFFFF);
out.putInt(text.index);
out.putInt(0x00000008);
out.putInt(0x00000000);
}
// children
for (NodeImpl child : children) {
child.write(out);
}
// end tag
out.putInt(RES_XML_END_ELEMENT_TYPE | (0x0010 << 16));
out.putInt(24);
out.putInt(-1);
out.putInt(0xFFFFFFFF);
out.putInt(ns != null ? this.ns.index : -1);
out.putInt(name.index);
}
}
static class Ns {
int ln;
StringItem prefix;
StringItem uri;
public Ns(StringItem prefix, StringItem uri, int ln) {
super();
this.prefix = prefix;
this.uri = uri;
this.ln = ln;
}
}
private List<NodeImpl> firsts = new ArrayList<NodeImpl>(3);
private Map<String, Ns> nses = new HashMap<String, Ns>();
private List<StringItem> otherString = new ArrayList<StringItem>();
private Map<String, StringItem> resourceId2Str = new HashMap<String, StringItem>();
private List<Integer> resourceIds = new ArrayList<Integer>();
private List<StringItem> resourceString = new ArrayList<StringItem>();
private StringItems stringItems = new StringItems();
// TODO add style support
// private List<StringItem> styleItems = new ArrayList();
@Override
public NodeVisitor child(String ns, String name) {
NodeImpl first = new NodeImpl(ns, name);
this.firsts.add(first);
return first;
}
@Override
public void end() {
}
@Override
public void ns(String prefix, String uri, int ln) {
nses.put(uri, new Ns(prefix == null ? null : new StringItem(prefix), new StringItem(uri), ln));
}
private int prepare() throws IOException {
int size = 0;
for (NodeImpl first : firsts) {
size += first.prepare(this);
}
{
int a = 0;
for (Map.Entry<String, Ns> e : nses.entrySet()) {
Ns ns = e.getValue();
if (ns == null) {
ns = new Ns(null, new StringItem(e.getKey()), 0);
e.setValue(ns);
}
if (ns.prefix == null) {
ns.prefix = new StringItem(String.format("axml_auto_%02d", a++));
}
ns.prefix = update(ns.prefix);
ns.uri = update(ns.uri);
}
}
size += nses.size() * 24 * 2;
this.stringItems.addAll(resourceString);
resourceString = null;
this.stringItems.addAll(otherString);
otherString = null;
this.stringItems.prepare();
int stringSize = this.stringItems.getSize();
if (stringSize % 4 != 0) {
stringSize += 4 - stringSize % 4;
}
size += 8 + stringSize;
size += 8 + resourceIds.size() * 4;
return size;
}
public byte[] toByteArray() throws IOException {
int size = 8 + prepare();
ByteBuffer out = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN);
out.putInt(RES_XML_TYPE | (0x0008 << 16));
out.putInt(size);
int stringSize = this.stringItems.getSize();
int padding = 0;
if (stringSize % 4 != 0) {
padding = 4 - stringSize % 4;
}
out.putInt(RES_STRING_POOL_TYPE | (0x001C << 16));
out.putInt(stringSize + padding + 8);
this.stringItems.write(out);
out.put(new byte[padding]);
out.putInt(RES_XML_RESOURCE_MAP_TYPE | (0x0008 << 16));
out.putInt(8 + this.resourceIds.size() * 4);
for (Integer i : resourceIds) {
out.putInt(i);
}
Stack<Ns> stack = new Stack<Ns>();
for (Map.Entry<String, Ns> e : this.nses.entrySet()) {
Ns ns = e.getValue();
stack.push(ns);
out.putInt(RES_XML_START_NAMESPACE_TYPE | (0x0010 << 16));
out.putInt(24);
out.putInt(-1);
out.putInt(0xFFFFFFFF);
out.putInt(ns.prefix.index);
out.putInt(ns.uri.index);
}
for (NodeImpl first : firsts) {
first.write(out);
}
while (stack.size() > 0) {
Ns ns = stack.pop();
out.putInt(RES_XML_END_NAMESPACE_TYPE | (0x0010 << 16));
out.putInt(24);
out.putInt(ns.ln);
out.putInt(0xFFFFFFFF);
out.putInt(ns.prefix.index);
out.putInt(ns.uri.index);
}
return out.array();
}
StringItem update(StringItem item) {
if (item == null)
return null;
int i = this.otherString.indexOf(item);
if (i < 0) {
StringItem copy = new StringItem(item.data);
this.otherString.add(copy);
return copy;
} else {
return this.otherString.get(i);
}
}
StringItem updateNs(StringItem item) {
if (item == null) {
return null;
}
String ns = item.data;
if (!this.nses.containsKey(ns)) {
this.nses.put(ns, null);
}
return update(item);
}
StringItem updateWithResourceId(StringItem name, int resourceId) {
String key = name.data + resourceId;
StringItem item = this.resourceId2Str.get(key);
if (item != null) {
return item;
} else {
StringItem copy = new StringItem(name.data);
resourceIds.add(resourceId);
resourceString.add(copy);
resourceId2Str.put(key, copy);
return copy;
}
}
}

View File

@ -0,0 +1,116 @@
/*
* Copyright (c) 2009-2013 Panxiaobo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package pxb.android.axml;
import java.util.HashMap;
import java.util.Map;
/**
* dump axml to stdout
*
* @author <a href="mailto:pxb1988@gmail.com">Panxiaobo</a>
*/
public class DumpAdapter extends AxmlVisitor {
protected int deep;
protected Map<String, String> nses;
public DumpAdapter() {
this(null);
}
public DumpAdapter(NodeVisitor nv) {
this(nv, 0, new HashMap<String, String>());
}
public DumpAdapter(NodeVisitor nv, int x, Map<String, String> nses) {
super(nv);
this.deep = x;
this.nses = nses;
}
@Override
public void attr(String ns, String name, int resourceId, int type, Object obj) {
for (int i = 0; i < deep; i++) {
System.out.print(" ");
}
if (ns != null) {
System.out.print(String.format("%s:", getPrefix(ns)));
}
System.out.print(name);
if (resourceId != -1) {
System.out.print(String.format("(%08x)", resourceId));
}
if (obj instanceof String) {
System.out.print(String.format("=[%08x]\"%s\"", type, obj));
} else if (obj instanceof Boolean) {
System.out.print(String.format("=[%08x]\"%b\"", type, obj));
} else if (obj instanceof ValueWrapper) {
ValueWrapper w = (ValueWrapper) obj;
System.out.print(String.format("=[%08x]@%08x, raw: \"%s\"", type, w.ref, w.raw));
} else if (type == TYPE_REFERENCE) {
System.out.print(String.format("=[%08x]@%08x", type, obj));
} else {
System.out.print(String.format("=[%08x]%08x", type, obj));
}
System.out.println();
super.attr(ns, name, resourceId, type, obj);
}
@Override
public NodeVisitor child(String ns, String name) {
for (int i = 0; i < deep; i++) {
System.out.print(" ");
}
System.out.print("<");
if (ns != null) {
System.out.print(getPrefix(ns) + ":");
}
System.out.println(name);
NodeVisitor nv = super.child(ns, name);
if (nv != null) {
return new DumpAdapter(nv, deep + 1, nses);
}
return null;
}
protected String getPrefix(String uri) {
if (nses != null) {
String prefix = nses.get(uri);
if (prefix != null) {
return prefix;
}
}
return uri;
}
@Override
public void ns(String prefix, String uri, int ln) {
System.out.println(prefix + "=" + uri);
this.nses.put(uri, prefix);
super.ns(prefix, uri, ln);
}
@Override
public void text(int ln, String value) {
for (int i = 0; i < deep + 1; i++) {
System.out.print(" ");
}
System.out.print("T: ");
System.out.println(value);
super.text(ln, value);
}
}

View File

@ -0,0 +1,97 @@
/*
* Copyright (c) 2009-2013 Panxiaobo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package pxb.android.axml;
public abstract class NodeVisitor {
public static final int TYPE_FIRST_INT = 0x10;
public static final int TYPE_INT_BOOLEAN = 0x12;
public static final int TYPE_INT_HEX = 0x11;
public static final int TYPE_REFERENCE = 0x01;
public static final int TYPE_STRING = 0x03;
protected NodeVisitor nv;
public NodeVisitor() {
super();
}
public NodeVisitor(NodeVisitor nv) {
super();
this.nv = nv;
}
/**
* add attribute to the node
*
* @param ns
* @param name
* @param resourceId
* @param type
* {@link #TYPE_STRING} or others
* @param obj
* a string for {@link #TYPE_STRING} ,and Integer for others
*/
public void attr(String ns, String name, int resourceId, int type, Object obj) {
if (nv != null) {
nv.attr(ns, name, resourceId, type, obj);
}
}
/**
* create a child node
*
* @param ns
* @param name
* @return
*/
public NodeVisitor child(String ns, String name) {
if (nv != null) {
return nv.child(ns, name);
}
return null;
}
/**
* end the visit
*/
public void end() {
if (nv != null) {
nv.end();
}
}
/**
* line number in the .xml
*
* @param ln
*/
public void line(int ln) {
if (nv != null) {
nv.line(ln);
}
}
/**
* the node text
*
* @param value
*/
public void text(int lineNumber, String value) {
if (nv != null) {
nv.text(lineNumber, value);
}
}
}

View File

@ -0,0 +1,78 @@
/*
* Copyright (c) 2009-2013 Panxiaobo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package pxb.android.axml;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
public class Util {
public static byte[] readFile(File in) throws IOException {
InputStream is = new FileInputStream(in);
byte[] xml = new byte[is.available()];
is.read(xml);
is.close();
return xml;
}
public static byte[] readIs(InputStream is) throws IOException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
copy(is, os);
return os.toByteArray();
}
public static void writeFile(byte[] data, File out) throws IOException {
FileOutputStream fos = new FileOutputStream(out);
fos.write(data);
fos.close();
}
public static Map<String, String> readProguardConfig(File config) throws IOException {
Map<String, String> clzMap = new HashMap<String, String>();
BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(config), "utf8"));
try {
for (String ln = r.readLine(); ln != null; ln = r.readLine()) {
if (ln.startsWith("#") || ln.startsWith(" ")) {
continue;
}
// format a.pt.Main -> a.a.a:
int i = ln.indexOf("->");
if (i > 0) {
clzMap.put(ln.substring(0, i).trim(), ln.substring(i + 2, ln.length() - 1).trim());
}
}
} finally {
r.close();
}
return clzMap;
}
public static void copy(InputStream is, OutputStream os) throws IOException {
byte[] xml = new byte[10 * 1024];
for (int c = is.read(xml); c > 0; c = is.read(xml)) {
os.write(xml, 0, c);
}
}
}

View File

@ -0,0 +1,34 @@
package pxb.android.axml;
public class ValueWrapper {
public static final int ID = 1;
public static final int STYLE = 2;
public static final int CLASS = 3;
public final int type;
public final String raw;
public final int ref;
private ValueWrapper(int type, int ref, String raw) {
super();
this.type = type;
this.raw = raw;
this.ref = ref;
}
public ValueWrapper replaceRaw(String raw) {
return new ValueWrapper(type, ref, raw);
}
public static ValueWrapper wrapId(int ref, String raw) {
return new ValueWrapper(ID, ref, raw);
}
public static ValueWrapper wrapStyle(int ref, String raw) {
return new ValueWrapper(STYLE, ref, raw);
}
public static ValueWrapper wrapClass(int ref, String raw) {
return new ValueWrapper(CLASS, ref, raw);
}
}

View File

@ -0,0 +1,2 @@
<p>Source code of <a href="https://code.google.com/p/axml/">axml project</a>.</p>
<p>Revision 2394d02d86bb - Apr 22, 2014</p>