Use AXML instead of Apkparser
This commit is contained in:
parent
76f438f959
commit
43c4c92365
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
if(isModule) {
|
||||
xposedminversion = Integer.parseInt(ap.getApkMeta().metaData.get("xposedminversion"));
|
||||
xposedsharedprefs = ap.getApkMeta().metaData.containsKey("xposedsharedprefs");
|
||||
try {
|
||||
Map<String, Object> metaData = MetaDataReader.getMetaData(new File(m));
|
||||
isModule = metaData.containsKey("xposedmodule");
|
||||
if (isModule) {
|
||||
xposedminversion = (Integer) metaData.get("xposedminversion");
|
||||
xposedsharedprefs = metaData.containsKey("xposedsharedprefs");
|
||||
}
|
||||
} catch (NumberFormatException | IOException e) {
|
||||
Log.w(TAG, "Apk parser fails: " + e);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Pkg> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
Loading…
Reference in New Issue