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"]
|
[submodule "edxp-core/src/main/cpp/external/Dobby"]
|
||||||
path = edxp-core/src/main/cpp/external/Dobby
|
path = edxp-core/src/main/cpp/external/Dobby
|
||||||
url = https://github.com/jmpews/Dobby.git
|
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')
|
api project(':xposed-bridge')
|
||||||
compileOnly project(':dexmaker')
|
compileOnly project(':dexmaker')
|
||||||
compileOnly 'com.android.support:support-annotations:28.0.0'
|
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.config.ConfigManager;
|
||||||
import com.elderdrivers.riru.edxp.util.Hookers;
|
import com.elderdrivers.riru.edxp.util.Hookers;
|
||||||
|
import com.elderdrivers.riru.edxp.util.MetaDataReader;
|
||||||
import com.elderdrivers.riru.edxp.util.Utils;
|
import com.elderdrivers.riru.edxp.util.Utils;
|
||||||
import com.jaredrummler.apkparser.ApkParser;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import de.robv.android.xposed.XC_MethodHook;
|
import de.robv.android.xposed.XC_MethodHook;
|
||||||
import de.robv.android.xposed.XposedBridge;
|
import de.robv.android.xposed.XposedBridge;
|
||||||
|
|
@ -61,11 +62,12 @@ public class HandleBindApp extends XC_MethodHook {
|
||||||
boolean isModule = false;
|
boolean isModule = false;
|
||||||
int xposedminversion = -1;
|
int xposedminversion = -1;
|
||||||
boolean xposedsharedprefs = false;
|
boolean xposedsharedprefs = false;
|
||||||
try (ApkParser ap = ApkParser.create(new File(appInfo.sourceDir))){
|
try {
|
||||||
isModule = ap.getApkMeta().metaData.containsKey("xposedmodule");
|
Map<String, Object> metaData = MetaDataReader.getMetaData(new File(appInfo.sourceDir));
|
||||||
|
isModule = metaData.containsKey("xposedmodule");
|
||||||
if (isModule) {
|
if (isModule) {
|
||||||
xposedminversion = Integer.parseInt(ap.getApkMeta().metaData.get("xposedminversion"));
|
xposedminversion = (Integer) metaData.get("xposedminversion");
|
||||||
xposedsharedprefs = ap.getApkMeta().metaData.containsKey("xposedsharedprefs");
|
xposedsharedprefs = metaData.containsKey("xposedsharedprefs");
|
||||||
}
|
}
|
||||||
} catch (NumberFormatException | IOException e) {
|
} catch (NumberFormatException | IOException e) {
|
||||||
Hookers.logE("ApkParser fails", 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 {
|
dependencies {
|
||||||
compileOnly project(':apk-parser:library')
|
|
||||||
compileOnly files(project(":dexmaker").tasks.getByName("makeJarRelease").outputs)
|
compileOnly files(project(":dexmaker").tasks.getByName("makeJarRelease").outputs)
|
||||||
compileOnly files(project(":hiddenapi-stubs").tasks.getByName("makeStubJar").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;
|
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.Member;
|
||||||
import java.lang.reflect.Modifier;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
import static de.robv.android.xposed.XposedBridge.hookMethodNative;
|
import static de.robv.android.xposed.XposedBridge.hookMethodNative;
|
||||||
import static de.robv.android.xposed.XposedBridge.log;
|
|
||||||
|
|
||||||
public final class PendingHooks {
|
public final class PendingHooks {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,14 @@
|
||||||
package de.robv.android.xposed;
|
package de.robv.android.xposed;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.ActivityThread;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageInfo;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.android.internal.util.XmlUtils;
|
import com.android.internal.util.XmlUtils;
|
||||||
import com.jaredrummler.apkparser.ApkParser;
|
import com.elderdrivers.riru.edxp.util.MetaDataReader;
|
||||||
|
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
||||||
|
|
@ -76,11 +73,12 @@ public final class XSharedPreferences implements SharedPreferences {
|
||||||
boolean isModule = false;
|
boolean isModule = false;
|
||||||
int xposedminversion = -1;
|
int xposedminversion = -1;
|
||||||
boolean xposedsharedprefs = false;
|
boolean xposedsharedprefs = false;
|
||||||
try (ApkParser ap = ApkParser.create(new File(m))) {
|
try {
|
||||||
isModule = ap.getApkMeta().metaData.containsKey("xposedmodule");
|
Map<String, Object> metaData = MetaDataReader.getMetaData(new File(m));
|
||||||
if(isModule) {
|
isModule = metaData.containsKey("xposedmodule");
|
||||||
xposedminversion = Integer.parseInt(ap.getApkMeta().metaData.get("xposedminversion"));
|
if (isModule) {
|
||||||
xposedsharedprefs = ap.getApkMeta().metaData.containsKey("xposedsharedprefs");
|
xposedminversion = (Integer) metaData.get("xposedminversion");
|
||||||
|
xposedsharedprefs = metaData.containsKey("xposedsharedprefs");
|
||||||
}
|
}
|
||||||
} catch (NumberFormatException | IOException e) {
|
} catch (NumberFormatException | IOException e) {
|
||||||
Log.w(TAG, "Apk parser fails: " + 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