code cleanup of 2014

This commit is contained in:
Connor Tumbleson 2014-02-09 19:01:57 -06:00
parent 720c08608d
commit 086139a037
50 changed files with 4194 additions and 4194 deletions

View File

@ -22,18 +22,18 @@ import brut.common.BrutException;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public class AndrolibException extends BrutException { public class AndrolibException extends BrutException {
public AndrolibException() { public AndrolibException() {
} }
public AndrolibException(String message) { public AndrolibException(String message) {
super(message); super(message);
} }
public AndrolibException(String message, Throwable cause) { public AndrolibException(String message, Throwable cause) {
super(message, cause); super(message, cause);
} }
public AndrolibException(Throwable cause) { public AndrolibException(Throwable cause) {
super(cause); super(cause);
} }
} }

View File

@ -107,25 +107,25 @@ public class ApkDecoder {
zef.close(); zef.close();
switch (mDecodeResources) { switch (mDecodeResources) {
case DECODE_RESOURCES_NONE: case DECODE_RESOURCES_NONE:
mAndrolib.decodeResourcesRaw(mApkFile, outDir); mAndrolib.decodeResourcesRaw(mApkFile, outDir);
break; break;
case DECODE_RESOURCES_FULL: case DECODE_RESOURCES_FULL:
mAndrolib.decodeResourcesFull(mApkFile, outDir, getResTable()); mAndrolib.decodeResourcesFull(mApkFile, outDir, getResTable());
break; break;
} }
} else { } else {
// if there's no resources.asrc, decode the manifest without looking // if there's no resources.asrc, decode the manifest without looking
// up attribute references // up attribute references
if (hasManifest()) { if (hasManifest()) {
switch (mDecodeResources) { switch (mDecodeResources) {
case DECODE_RESOURCES_NONE: case DECODE_RESOURCES_NONE:
mAndrolib.decodeManifestRaw(mApkFile, outDir); mAndrolib.decodeManifestRaw(mApkFile, outDir);
break; break;
case DECODE_RESOURCES_FULL: case DECODE_RESOURCES_FULL:
mAndrolib.decodeManifestFull(mApkFile, outDir, mAndrolib.decodeManifestFull(mApkFile, outDir,
getResTable()); getResTable());
break; break;
} }
} }
} }

View File

@ -36,128 +36,128 @@ import org.apache.commons.io.IOUtils;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public class ResSmaliUpdater { public class ResSmaliUpdater {
public void tagResIDs(ResTable resTable, File smaliDir) public void tagResIDs(ResTable resTable, File smaliDir)
throws AndrolibException { throws AndrolibException {
Directory dir = null; Directory dir = null;
try { try {
dir = new FileDirectory(smaliDir); dir = new FileDirectory(smaliDir);
} catch (DirectoryException ex) { } catch (DirectoryException ex) {
throw new AndrolibException("Could not tag res IDs", ex); throw new AndrolibException("Could not tag res IDs", ex);
} }
for (String fileName : dir.getFiles(true)) { for (String fileName : dir.getFiles(true)) {
try { try {
tagResIdsForFile(resTable, dir, fileName); tagResIdsForFile(resTable, dir, fileName);
} catch (IOException ex) { } catch (IOException ex) {
throw new AndrolibException("Could not tag resIDs for file: " throw new AndrolibException("Could not tag resIDs for file: "
+ fileName, ex); + fileName, ex);
} catch (DirectoryException ex) { } catch (DirectoryException ex) {
throw new AndrolibException("Could not tag resIDs for file: " throw new AndrolibException("Could not tag resIDs for file: "
+ fileName, ex); + fileName, ex);
} catch (AndrolibException ex) { } catch (AndrolibException ex) {
throw new AndrolibException("Could not tag resIDs for file: " throw new AndrolibException("Could not tag resIDs for file: "
+ fileName, ex); + fileName, ex);
} }
} }
} }
public void updateResIDs(ResTable resTable, File smaliDir) public void updateResIDs(ResTable resTable, File smaliDir)
throws AndrolibException { throws AndrolibException {
try { try {
Directory dir = new FileDirectory(smaliDir); Directory dir = new FileDirectory(smaliDir);
for (String fileName : dir.getFiles(true)) { for (String fileName : dir.getFiles(true)) {
Iterator<String> it = IOUtils.readLines( Iterator<String> it = IOUtils.readLines(
dir.getFileInput(fileName)).iterator(); dir.getFileInput(fileName)).iterator();
PrintWriter out = new PrintWriter(dir.getFileOutput(fileName)); PrintWriter out = new PrintWriter(dir.getFileOutput(fileName));
while (it.hasNext()) { while (it.hasNext()) {
String line = it.next(); String line = it.next();
out.println(line); out.println(line);
Matcher m1 = RES_NAME_PATTERN.matcher(line); Matcher m1 = RES_NAME_PATTERN.matcher(line);
if (!m1.matches()) { if (!m1.matches()) {
continue; continue;
} }
Matcher m2 = RES_ID_PATTERN.matcher(it.next()); Matcher m2 = RES_ID_PATTERN.matcher(it.next());
if (!m2.matches()) { if (!m2.matches()) {
throw new AndrolibException(); throw new AndrolibException();
} }
int resID = resTable.getPackage(m1.group(1)) int resID = resTable.getPackage(m1.group(1))
.getType(m1.group(2)).getResSpec(m1.group(3)) .getType(m1.group(2)).getResSpec(m1.group(3))
.getId().id; .getId().id;
if (m2.group(1) != null) { if (m2.group(1) != null) {
out.println(String.format(RES_ID_FORMAT_FIELD, out.println(String.format(RES_ID_FORMAT_FIELD,
m2.group(1), resID)); m2.group(1), resID));
} else { } else {
out.println(String.format(RES_ID_FORMAT_CONST, out.println(String.format(RES_ID_FORMAT_CONST,
m2.group(2), resID)); m2.group(2), resID));
} }
} }
out.close(); out.close();
} }
} catch (IOException ex) { } catch (IOException ex) {
throw new AndrolibException("Could not tag res IDs for: " throw new AndrolibException("Could not tag res IDs for: "
+ smaliDir.getAbsolutePath(), ex); + smaliDir.getAbsolutePath(), ex);
} catch (DirectoryException ex) { } catch (DirectoryException ex) {
throw new AndrolibException("Could not tag res IDs for: " throw new AndrolibException("Could not tag res IDs for: "
+ smaliDir.getAbsolutePath(), ex); + smaliDir.getAbsolutePath(), ex);
} }
} }
private void tagResIdsForFile(ResTable resTable, Directory dir, private void tagResIdsForFile(ResTable resTable, Directory dir,
String fileName) throws IOException, DirectoryException, String fileName) throws IOException, DirectoryException,
AndrolibException { AndrolibException {
Iterator<String> it = IOUtils.readLines(dir.getFileInput(fileName)) Iterator<String> it = IOUtils.readLines(dir.getFileInput(fileName))
.iterator(); .iterator();
PrintWriter out = new PrintWriter(dir.getFileOutput(fileName)); PrintWriter out = new PrintWriter(dir.getFileOutput(fileName));
while (it.hasNext()) { while (it.hasNext()) {
String line = it.next(); String line = it.next();
if (RES_NAME_PATTERN.matcher(line).matches()) { if (RES_NAME_PATTERN.matcher(line).matches()) {
out.println(line); out.println(line);
out.println(it.next()); out.println(it.next());
continue; continue;
} }
Matcher m = RES_ID_PATTERN.matcher(line); Matcher m = RES_ID_PATTERN.matcher(line);
if (m.matches()) { if (m.matches()) {
int resID = parseResID(m.group(3)); int resID = parseResID(m.group(3));
if (resID != -1) { if (resID != -1) {
try { try {
ResResSpec spec = resTable.getResSpec(resID); ResResSpec spec = resTable.getResSpec(resID);
out.println(String.format(RES_NAME_FORMAT, out.println(String.format(RES_NAME_FORMAT,
spec.getFullName())); spec.getFullName()));
} catch (UndefinedResObject ex) { } catch (UndefinedResObject ex) {
if (!R_FILE_PATTERN.matcher(fileName).matches()) { if (!R_FILE_PATTERN.matcher(fileName).matches()) {
LOGGER.warning(String.format( LOGGER.warning(String.format(
"Undefined resource spec in %s: 0x%08x", "Undefined resource spec in %s: 0x%08x",
fileName, resID)); fileName, resID));
} }
} }
} }
} }
out.println(line); out.println(line);
} }
out.close(); out.close();
} }
private int parseResID(String resIDHex) { private int parseResID(String resIDHex) {
if (resIDHex.endsWith("ff")) { if (resIDHex.endsWith("ff")) {
return -1; return -1;
} }
int resID = Integer.valueOf(resIDHex, 16); int resID = Integer.valueOf(resIDHex, 16);
if (resIDHex.length() == 4) { if (resIDHex.length() == 4) {
resID = resID << 16; resID = resID << 16;
} }
return resID; return resID;
} }
private final static String RES_ID_FORMAT_FIELD = ".field %s:I = 0x%08x"; private final static String RES_ID_FORMAT_FIELD = ".field %s:I = 0x%08x";
private final static String RES_ID_FORMAT_CONST = " const %s, 0x%08x"; private final static String RES_ID_FORMAT_CONST = " const %s, 0x%08x";
private final static Pattern RES_ID_PATTERN = Pattern private final static Pattern RES_ID_PATTERN = Pattern
.compile("^(?:\\.field (.+?):I =| const(?:|/(?:|high)16) ([pv]\\d+?),) 0x(7[a-f]0[1-9a-f](?:|[0-9a-f]{4}))$"); .compile("^(?:\\.field (.+?):I =| const(?:|/(?:|high)16) ([pv]\\d+?),) 0x(7[a-f]0[1-9a-f](?:|[0-9a-f]{4}))$");
private final static String RES_NAME_FORMAT = "# APKTOOL/RES_NAME: %s"; private final static String RES_NAME_FORMAT = "# APKTOOL/RES_NAME: %s";
private final static Pattern RES_NAME_PATTERN = Pattern private final static Pattern RES_NAME_PATTERN = Pattern
.compile("^# APKTOOL/RES_NAME: ([a-zA-Z0-9.]+):([a-z]+)/([a-zA-Z0-9._]+)$"); .compile("^# APKTOOL/RES_NAME: ([a-zA-Z0-9.]+):([a-z]+)/([a-zA-Z0-9._]+)$");
private final static Pattern R_FILE_PATTERN = Pattern private final static Pattern R_FILE_PATTERN = Pattern
.compile(".*R\\$[a-z]+\\.smali$"); .compile(".*R\\$[a-z]+\\.smali$");
private final static Logger LOGGER = Logger.getLogger(ResSmaliUpdater.class private final static Logger LOGGER = Logger.getLogger(ResSmaliUpdater.class
.getName()); .getName());
} }

View File

@ -24,49 +24,49 @@ import java.util.*;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public class ResConfig { public class ResConfig {
private final ResConfigFlags mFlags; private final ResConfigFlags mFlags;
private final Map<ResResSpec, ResResource> mResources = new LinkedHashMap<ResResSpec, ResResource>(); private final Map<ResResSpec, ResResource> mResources = new LinkedHashMap<ResResSpec, ResResource>();
public ResConfig(ResConfigFlags flags) { public ResConfig(ResConfigFlags flags) {
this.mFlags = flags; this.mFlags = flags;
} }
public Set<ResResource> listResources() { public Set<ResResource> listResources() {
return new LinkedHashSet<ResResource>(mResources.values()); return new LinkedHashSet<ResResource>(mResources.values());
} }
public ResResource getResource(ResResSpec spec) throws AndrolibException { public ResResource getResource(ResResSpec spec) throws AndrolibException {
ResResource res = mResources.get(spec); ResResource res = mResources.get(spec);
if (res == null) { if (res == null) {
throw new UndefinedResObject(String.format( throw new UndefinedResObject(String.format(
"resource: spec=%s, config=%s", spec, this)); "resource: spec=%s, config=%s", spec, this));
} }
return res; return res;
} }
public Set<ResResSpec> listResSpecs() { public Set<ResResSpec> listResSpecs() {
return mResources.keySet(); return mResources.keySet();
} }
public ResConfigFlags getFlags() { public ResConfigFlags getFlags() {
return mFlags; return mFlags;
} }
public void addResource(ResResource res) throws AndrolibException { public void addResource(ResResource res) throws AndrolibException {
addResource(res, false); addResource(res, false);
} }
public void addResource(ResResource res, boolean overwrite) public void addResource(ResResource res, boolean overwrite)
throws AndrolibException { throws AndrolibException {
ResResSpec spec = res.getResSpec(); ResResSpec spec = res.getResSpec();
if (mResources.put(spec, res) != null && !overwrite) { if (mResources.put(spec, res) != null && !overwrite) {
throw new AndrolibException(String.format( throw new AndrolibException(String.format(
"Multiple resources: spec=%s, config=%s", spec, this)); "Multiple resources: spec=%s, config=%s", spec, this));
} }
} }
@Override @Override
public String toString() { public String toString() {
return mFlags.toString(); return mFlags.toString();
} }
} }

View File

@ -20,51 +20,51 @@ package brut.androlib.res.data;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public class ResID { public class ResID {
public final int package_; public final int package_;
public final int type; public final int type;
public final int entry; public final int entry;
public final int id; public final int id;
public ResID(int package_, int type, int entry) { public ResID(int package_, int type, int entry) {
this(package_, type, entry, (package_ << 24) + (type << 16) + entry); this(package_, type, entry, (package_ << 24) + (type << 16) + entry);
} }
public ResID(int id) { public ResID(int id) {
this(id >> 24, (id >> 16) & 0x000000ff, id & 0x0000ffff, id); this(id >> 24, (id >> 16) & 0x000000ff, id & 0x0000ffff, id);
} }
public ResID(int package_, int type, int entry, int id) { public ResID(int package_, int type, int entry, int id) {
this.package_ = package_; this.package_ = package_;
this.type = type; this.type = type;
this.entry = entry; this.entry = entry;
this.id = id; this.id = id;
} }
@Override @Override
public String toString() { public String toString() {
return String.format("0x%08x", id); return String.format("0x%08x", id);
} }
@Override @Override
public int hashCode() { public int hashCode() {
int hash = 17; int hash = 17;
hash = 31 * hash + this.id; hash = 31 * hash + this.id;
return hash; return hash;
} }
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (obj == null) { if (obj == null) {
return false; return false;
} }
if (getClass() != obj.getClass()) { if (getClass() != obj.getClass()) {
return false; return false;
} }
final ResID other = (ResID) obj; final ResID other = (ResID) obj;
if (this.id != other.id) { if (this.id != other.id) {
return false; return false;
} }
return true; return true;
} }
} }

View File

@ -29,200 +29,200 @@ import java.util.logging.Logger;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public class ResPackage { public class ResPackage {
private final ResTable mResTable; private final ResTable mResTable;
private final int mId; private final int mId;
private final String mName; private final String mName;
private final Map<ResID, ResResSpec> mResSpecs = new LinkedHashMap<ResID, ResResSpec>(); private final Map<ResID, ResResSpec> mResSpecs = new LinkedHashMap<ResID, ResResSpec>();
private final Map<ResConfigFlags, ResConfig> mConfigs = new LinkedHashMap<ResConfigFlags, ResConfig>(); private final Map<ResConfigFlags, ResConfig> mConfigs = new LinkedHashMap<ResConfigFlags, ResConfig>();
private final Map<String, ResType> mTypes = new LinkedHashMap<String, ResType>(); private final Map<String, ResType> mTypes = new LinkedHashMap<String, ResType>();
private final Set<ResID> mSynthesizedRes = new HashSet<ResID>(); private final Set<ResID> mSynthesizedRes = new HashSet<ResID>();
private ResValueFactory mValueFactory; private ResValueFactory mValueFactory;
public ResPackage(ResTable resTable, int id, String name) { public ResPackage(ResTable resTable, int id, String name) {
this.mResTable = resTable; this.mResTable = resTable;
this.mId = id; this.mId = id;
this.mName = name; this.mName = name;
} }
public List<ResResSpec> listResSpecs() { public List<ResResSpec> listResSpecs() {
return new ArrayList<ResResSpec>(mResSpecs.values()); return new ArrayList<ResResSpec>(mResSpecs.values());
} }
public boolean hasResSpec(ResID resID) { public boolean hasResSpec(ResID resID) {
return mResSpecs.containsKey(resID); return mResSpecs.containsKey(resID);
} }
public ResResSpec getResSpec(ResID resID) throws UndefinedResObject { public ResResSpec getResSpec(ResID resID) throws UndefinedResObject {
ResResSpec spec = mResSpecs.get(resID); ResResSpec spec = mResSpecs.get(resID);
if (spec == null) { if (spec == null) {
throw new UndefinedResObject("resource spec: " + resID.toString()); throw new UndefinedResObject("resource spec: " + resID.toString());
} }
return spec; return spec;
} }
public List<ResConfig> getConfigs() { public List<ResConfig> getConfigs() {
return new ArrayList<ResConfig>(mConfigs.values()); return new ArrayList<ResConfig>(mConfigs.values());
} }
public boolean hasConfig(ResConfigFlags flags) { public boolean hasConfig(ResConfigFlags flags) {
return mConfigs.containsKey(flags); return mConfigs.containsKey(flags);
} }
public ResConfig getConfig(ResConfigFlags flags) throws AndrolibException { public ResConfig getConfig(ResConfigFlags flags) throws AndrolibException {
ResConfig config = mConfigs.get(flags); ResConfig config = mConfigs.get(flags);
if (config == null) { if (config == null) {
throw new UndefinedResObject("config: " + flags); throw new UndefinedResObject("config: " + flags);
} }
return config; return config;
} }
public int getResSpecCount() { public int getResSpecCount() {
return mResSpecs.size(); return mResSpecs.size();
} }
public ResConfig getOrCreateConfig(ResConfigFlags flags) public ResConfig getOrCreateConfig(ResConfigFlags flags)
throws AndrolibException { throws AndrolibException {
ResConfig config = mConfigs.get(flags); ResConfig config = mConfigs.get(flags);
if (config == null) { if (config == null) {
config = new ResConfig(flags); config = new ResConfig(flags);
mConfigs.put(flags, config); mConfigs.put(flags, config);
} }
return config; return config;
} }
public List<ResType> listTypes() { public List<ResType> listTypes() {
return new ArrayList<ResType>(mTypes.values()); return new ArrayList<ResType>(mTypes.values());
} }
public boolean hasType(String typeName) { public boolean hasType(String typeName) {
return mTypes.containsKey(typeName); return mTypes.containsKey(typeName);
} }
public ResType getType(String typeName) throws AndrolibException { public ResType getType(String typeName) throws AndrolibException {
ResType type = mTypes.get(typeName); ResType type = mTypes.get(typeName);
if (type == null) { if (type == null) {
throw new UndefinedResObject("type: " + typeName); throw new UndefinedResObject("type: " + typeName);
} }
return type; return type;
} }
public Set<ResResource> listFiles() { public Set<ResResource> listFiles() {
Set<ResResource> ret = new HashSet<ResResource>(); Set<ResResource> ret = new HashSet<ResResource>();
for (ResResSpec spec : mResSpecs.values()) { for (ResResSpec spec : mResSpecs.values()) {
for (ResResource res : spec.listResources()) { for (ResResource res : spec.listResources()) {
if (res.getValue() instanceof ResFileValue) { if (res.getValue() instanceof ResFileValue) {
ret.add(res); ret.add(res);
} }
} }
} }
return ret; return ret;
} }
public Collection<ResValuesFile> listValuesFiles() { public Collection<ResValuesFile> listValuesFiles() {
Map<Duo<ResType, ResConfig>, ResValuesFile> ret = new HashMap<Duo<ResType, ResConfig>, ResValuesFile>(); Map<Duo<ResType, ResConfig>, ResValuesFile> ret = new HashMap<Duo<ResType, ResConfig>, ResValuesFile>();
for (ResResSpec spec : mResSpecs.values()) { for (ResResSpec spec : mResSpecs.values()) {
for (ResResource res : spec.listResources()) { for (ResResource res : spec.listResources()) {
if (res.getValue() instanceof ResValuesXmlSerializable) { if (res.getValue() instanceof ResValuesXmlSerializable) {
ResType type = res.getResSpec().getType(); ResType type = res.getResSpec().getType();
ResConfig config = res.getConfig(); ResConfig config = res.getConfig();
Duo<ResType, ResConfig> key = new Duo<ResType, ResConfig>( Duo<ResType, ResConfig> key = new Duo<ResType, ResConfig>(
type, config); type, config);
ResValuesFile values = ret.get(key); ResValuesFile values = ret.get(key);
if (values == null) { if (values == null) {
values = new ResValuesFile(this, type, config); values = new ResValuesFile(this, type, config);
ret.put(key, values); ret.put(key, values);
} }
values.addResource(res); values.addResource(res);
} }
} }
} }
return ret.values(); return ret.values();
} }
public ResTable getResTable() { public ResTable getResTable() {
return mResTable; return mResTable;
} }
public int getId() { public int getId() {
return mId; return mId;
} }
public String getName() { public String getName() {
return mName; return mName;
} }
boolean isSynthesized(ResID resId) { boolean isSynthesized(ResID resId) {
return mSynthesizedRes.contains(resId); return mSynthesizedRes.contains(resId);
} }
public void addResSpec(ResResSpec spec) throws AndrolibException { public void addResSpec(ResResSpec spec) throws AndrolibException {
if (mResSpecs.put(spec.getId(), spec) != null) { if (mResSpecs.put(spec.getId(), spec) != null) {
throw new AndrolibException("Multiple resource specs: " + spec); throw new AndrolibException("Multiple resource specs: " + spec);
} }
} }
public void addConfig(ResConfig config) throws AndrolibException { public void addConfig(ResConfig config) throws AndrolibException {
if (mConfigs.put(config.getFlags(), config) != null) { if (mConfigs.put(config.getFlags(), config) != null) {
throw new AndrolibException("Multiple configs: " + config); throw new AndrolibException("Multiple configs: " + config);
} }
} }
public void addType(ResType type) throws AndrolibException { public void addType(ResType type) throws AndrolibException {
if (mTypes.containsKey(type.getName())) { if (mTypes.containsKey(type.getName())) {
LOGGER.warning("Multiple types detected! " + type + " ignored!"); LOGGER.warning("Multiple types detected! " + type + " ignored!");
} else { } else {
mTypes.put(type.getName(), type); mTypes.put(type.getName(), type);
} }
} }
public void addResource(ResResource res) { public void addResource(ResResource res) {
} }
public void addSynthesizedRes(int resId) { public void addSynthesizedRes(int resId) {
mSynthesizedRes.add(new ResID(resId)); mSynthesizedRes.add(new ResID(resId));
} }
@Override @Override
public String toString() { public String toString() {
return mName; return mName;
} }
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (obj == null) { if (obj == null) {
return false; return false;
} }
if (getClass() != obj.getClass()) { if (getClass() != obj.getClass()) {
return false; return false;
} }
final ResPackage other = (ResPackage) obj; final ResPackage other = (ResPackage) obj;
if (this.mResTable != other.mResTable if (this.mResTable != other.mResTable
&& (this.mResTable == null || !this.mResTable && (this.mResTable == null || !this.mResTable
.equals(other.mResTable))) { .equals(other.mResTable))) {
return false; return false;
} }
if (this.mId != other.mId) { if (this.mId != other.mId) {
return false; return false;
} }
return true; return true;
} }
@Override @Override
public int hashCode() { public int hashCode() {
int hash = 17; int hash = 17;
hash = 31 * hash hash = 31 * hash
+ (this.mResTable != null ? this.mResTable.hashCode() : 0); + (this.mResTable != null ? this.mResTable.hashCode() : 0);
hash = 31 * hash + this.mId; hash = 31 * hash + this.mId;
return hash; return hash;
} }
public ResValueFactory getValueFactory() { public ResValueFactory getValueFactory() {
if (mValueFactory == null) { if (mValueFactory == null) {
mValueFactory = new ResValueFactory(this); mValueFactory = new ResValueFactory(this);
} }
return mValueFactory; return mValueFactory;
} }
private final static Logger LOGGER = Logger private final static Logger LOGGER = Logger
.getLogger(ResPackage.class.getName()); .getLogger(ResPackage.class.getName());

View File

@ -25,97 +25,97 @@ import org.apache.commons.lang3.StringUtils;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public class ResResSpec { public class ResResSpec {
private final ResID mId; private final ResID mId;
private final String mName; private final String mName;
private final ResPackage mPackage; private final ResPackage mPackage;
private final ResType mType; private final ResType mType;
private final Map<ResConfigFlags, ResResource> mResources = new LinkedHashMap<ResConfigFlags, ResResource>(); private final Map<ResConfigFlags, ResResource> mResources = new LinkedHashMap<ResConfigFlags, ResResource>();
public ResResSpec(ResID id, String name, ResPackage pkg, ResType type) { public ResResSpec(ResID id, String name, ResPackage pkg, ResType type) {
this.mId = id; this.mId = id;
this.mName = name; this.mName = name;
this.mPackage = pkg; this.mPackage = pkg;
this.mType = type; this.mType = type;
} }
public Set<ResResource> listResources() { public Set<ResResource> listResources() {
return new LinkedHashSet<ResResource>(mResources.values()); return new LinkedHashSet<ResResource>(mResources.values());
} }
public ResResource getResource(ResConfig config) throws AndrolibException { public ResResource getResource(ResConfig config) throws AndrolibException {
return getResource(config.getFlags()); return getResource(config.getFlags());
} }
public ResResource getResource(ResConfigFlags config) public ResResource getResource(ResConfigFlags config)
throws AndrolibException { throws AndrolibException {
ResResource res = mResources.get(config); ResResource res = mResources.get(config);
if (res == null) { if (res == null) {
throw new UndefinedResObject(String.format( throw new UndefinedResObject(String.format(
"resource: spec=%s, config=%s", this, config)); "resource: spec=%s, config=%s", this, config));
} }
return res; return res;
} }
public boolean hasResource(ResConfig config) { public boolean hasResource(ResConfig config) {
return hasResource(config.getFlags()); return hasResource(config.getFlags());
} }
private boolean hasResource(ResConfigFlags flags) { private boolean hasResource(ResConfigFlags flags) {
return mResources.containsKey(flags); return mResources.containsKey(flags);
} }
public ResResource getDefaultResource() throws AndrolibException { public ResResource getDefaultResource() throws AndrolibException {
return getResource(new ResConfigFlags()); return getResource(new ResConfigFlags());
} }
public boolean hasDefaultResource() { public boolean hasDefaultResource() {
return mResources.containsKey(new ResConfigFlags()); return mResources.containsKey(new ResConfigFlags());
} }
public String getFullName() { public String getFullName() {
return getFullName(false, false); return getFullName(false, false);
} }
public String getFullName(ResPackage relativeToPackage, boolean excludeType) { public String getFullName(ResPackage relativeToPackage, boolean excludeType) {
return getFullName(getPackage().equals(relativeToPackage), excludeType); return getFullName(getPackage().equals(relativeToPackage), excludeType);
} }
public String getFullName(boolean excludePackage, boolean excludeType) { public String getFullName(boolean excludePackage, boolean excludeType) {
return (excludePackage ? "" : getPackage().getName() + ":") return (excludePackage ? "" : getPackage().getName() + ":")
+ (excludeType ? "" : getType().getName() + "/") + getName(); + (excludeType ? "" : getType().getName() + "/") + getName();
} }
public ResID getId() { public ResID getId() {
return mId; return mId;
} }
public String getName() { public String getName() {
return StringUtils.replace(mName, "\"", "q"); return StringUtils.replace(mName, "\"", "q");
} }
public ResPackage getPackage() { public ResPackage getPackage() {
return mPackage; return mPackage;
} }
public ResType getType() { public ResType getType() {
return mType; return mType;
} }
public void addResource(ResResource res) throws AndrolibException { public void addResource(ResResource res) throws AndrolibException {
addResource(res, false); addResource(res, false);
} }
public void addResource(ResResource res, boolean overwrite) public void addResource(ResResource res, boolean overwrite)
throws AndrolibException { throws AndrolibException {
ResConfigFlags flags = res.getConfig().getFlags(); ResConfigFlags flags = res.getConfig().getFlags();
if (mResources.put(flags, res) != null && !overwrite) { if (mResources.put(flags, res) != null && !overwrite) {
throw new AndrolibException(String.format( throw new AndrolibException(String.format(
"Multiple resources: spec=%s, config=%s", this, flags)); "Multiple resources: spec=%s, config=%s", this, flags));
} }
} }
@Override @Override
public String toString() { public String toString() {
return mId.toString() + " " + mType.toString() + "/" + mName; return mId.toString() + " " + mType.toString() + "/" + mName;
} }
} }

View File

@ -23,41 +23,41 @@ import brut.androlib.res.data.value.ResValue;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public class ResResource { public class ResResource {
private final ResConfig mConfig; private final ResConfig mConfig;
private final ResResSpec mResSpec; private final ResResSpec mResSpec;
private final ResValue mValue; private final ResValue mValue;
public ResResource(ResConfig config, ResResSpec spec, ResValue value) { public ResResource(ResConfig config, ResResSpec spec, ResValue value) {
this.mConfig = config; this.mConfig = config;
this.mResSpec = spec; this.mResSpec = spec;
this.mValue = value; this.mValue = value;
} }
public String getFilePath() { public String getFilePath() {
return mResSpec.getType().getName() return mResSpec.getType().getName()
+ mConfig.getFlags().getQualifiers() + "/" + mResSpec.getName(); + mConfig.getFlags().getQualifiers() + "/" + mResSpec.getName();
} }
public ResConfig getConfig() { public ResConfig getConfig() {
return mConfig; return mConfig;
} }
public ResResSpec getResSpec() { public ResResSpec getResSpec() {
return mResSpec; return mResSpec;
} }
public ResValue getValue() { public ResValue getValue() {
return mValue; return mValue;
} }
public void replace(ResValue value) throws AndrolibException { public void replace(ResValue value) throws AndrolibException {
ResResource res = new ResResource(mConfig, mResSpec, value); ResResource res = new ResResource(mConfig, mResSpec, value);
mConfig.addResource(res, true); mConfig.addResource(res, true);
mResSpec.addResource(res, true); mResSpec.addResource(res, true);
} }
@Override @Override
public String toString() { public String toString() {
return getFilePath(); return getFilePath();
} }
} }

View File

@ -26,57 +26,57 @@ import java.util.*;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public class ResTable { public class ResTable {
private final AndrolibResources mAndRes; private final AndrolibResources mAndRes;
private final Map<Integer, ResPackage> mPackagesById = new HashMap<Integer, ResPackage>(); private final Map<Integer, ResPackage> mPackagesById = new HashMap<Integer, ResPackage>();
private final Map<String, ResPackage> mPackagesByName = new HashMap<String, ResPackage>(); private final Map<String, ResPackage> mPackagesByName = new HashMap<String, ResPackage>();
private final Set<ResPackage> mMainPackages = new LinkedHashSet<ResPackage>(); private final Set<ResPackage> mMainPackages = new LinkedHashSet<ResPackage>();
private final Set<ResPackage> mFramePackages = new LinkedHashSet<ResPackage>(); private final Set<ResPackage> mFramePackages = new LinkedHashSet<ResPackage>();
private String mFrameTag; private String mFrameTag;
private String mPackageRenamed; private String mPackageRenamed;
private String mPackageOriginal; private String mPackageOriginal;
private int mPackageId; private int mPackageId;
private boolean mAnalysisMode = false; private boolean mAnalysisMode = false;
private Map<String, String> mSdkInfo = new LinkedHashMap<String, String>(); private Map<String, String> mSdkInfo = new LinkedHashMap<String, String>();
private Map<String, String> mVersionInfo = new LinkedHashMap<String, String>(); private Map<String, String> mVersionInfo = new LinkedHashMap<String, String>();
private Map<String, String> mUnknownFiles = new LinkedHashMap<String, String>(); private Map<String, String> mUnknownFiles = new LinkedHashMap<String, String>();
public ResTable() { public ResTable() {
mAndRes = null; mAndRes = null;
} }
public ResTable(AndrolibResources andRes) { public ResTable(AndrolibResources andRes) {
mAndRes = andRes; mAndRes = andRes;
} }
public ResResSpec getResSpec(int resID) throws AndrolibException { public ResResSpec getResSpec(int resID) throws AndrolibException {
return getResSpec(new ResID(resID)); return getResSpec(new ResID(resID));
} }
public ResResSpec getResSpec(ResID resID) throws AndrolibException { public ResResSpec getResSpec(ResID resID) throws AndrolibException {
return getPackage(resID.package_).getResSpec(resID); return getPackage(resID.package_).getResSpec(resID);
} }
public Set<ResPackage> listMainPackages() { public Set<ResPackage> listMainPackages() {
return mMainPackages; return mMainPackages;
} }
public Set<ResPackage> listFramePackages() { public Set<ResPackage> listFramePackages() {
return mFramePackages; return mFramePackages;
} }
public ResPackage getPackage(int id) throws AndrolibException { public ResPackage getPackage(int id) throws AndrolibException {
ResPackage pkg = mPackagesById.get(id); ResPackage pkg = mPackagesById.get(id);
if (pkg != null) { if (pkg != null) {
return pkg; return pkg;
} }
if (mAndRes != null) { if (mAndRes != null) {
return mAndRes.loadFrameworkPkg(this, id, mFrameTag); return mAndRes.loadFrameworkPkg(this, id, mFrameTag);
} }
throw new UndefinedResObject(String.format("package: id=%d", id)); throw new UndefinedResObject(String.format("package: id=%d", id));
} }
public ResPackage getHighestSpecPackage() throws AndrolibException { public ResPackage getHighestSpecPackage() throws AndrolibException {
int id = 0; int id = 0;
@ -91,52 +91,52 @@ public class ResTable {
return (id == 0) ? getPackage(1) : getPackage(id); return (id == 0) ? getPackage(1) : getPackage(id);
} }
public ResPackage getPackage(String name) throws AndrolibException { public ResPackage getPackage(String name) throws AndrolibException {
ResPackage pkg = mPackagesByName.get(name); ResPackage pkg = mPackagesByName.get(name);
if (pkg == null) { if (pkg == null) {
throw new UndefinedResObject("package: name=" + name); throw new UndefinedResObject("package: name=" + name);
} }
return pkg; return pkg;
} }
public boolean hasPackage(int id) { public boolean hasPackage(int id) {
return mPackagesById.containsKey(id); return mPackagesById.containsKey(id);
} }
public boolean hasPackage(String name) { public boolean hasPackage(String name) {
return mPackagesByName.containsKey(name); return mPackagesByName.containsKey(name);
} }
public ResValue getValue(String package_, String type, String name) public ResValue getValue(String package_, String type, String name)
throws AndrolibException { throws AndrolibException {
return getPackage(package_).getType(type).getResSpec(name) return getPackage(package_).getType(type).getResSpec(name)
.getDefaultResource().getValue(); .getDefaultResource().getValue();
} }
public void addPackage(ResPackage pkg, boolean main) public void addPackage(ResPackage pkg, boolean main)
throws AndrolibException { throws AndrolibException {
Integer id = pkg.getId(); Integer id = pkg.getId();
if (mPackagesById.containsKey(id)) { if (mPackagesById.containsKey(id)) {
throw new AndrolibException("Multiple packages: id=" throw new AndrolibException("Multiple packages: id="
+ id.toString()); + id.toString());
} }
String name = pkg.getName(); String name = pkg.getName();
if (mPackagesByName.containsKey(name)) { if (mPackagesByName.containsKey(name)) {
throw new AndrolibException("Multiple packages: name=" + name); throw new AndrolibException("Multiple packages: name=" + name);
} }
mPackagesById.put(id, pkg); mPackagesById.put(id, pkg);
mPackagesByName.put(name, pkg); mPackagesByName.put(name, pkg);
if (main) { if (main) {
mMainPackages.add(pkg); mMainPackages.add(pkg);
} else { } else {
mFramePackages.add(pkg); mFramePackages.add(pkg);
} }
} }
public void setFrameTag(String tag) { public void setFrameTag(String tag) {
mFrameTag = tag; mFrameTag = tag;
} }
public void setAnalysisMode(boolean mode) { public void setAnalysisMode(boolean mode) {
mAnalysisMode = mode; mAnalysisMode = mode;
@ -153,30 +153,30 @@ public class ResTable {
public void setPackageId(int id) { public void setPackageId(int id) {
mPackageId = id; mPackageId = id;
} }
public void clearSdkInfo() {
mSdkInfo.clear();
}
public void addSdkInfo(String key, String value) { public void clearSdkInfo() {
mSdkInfo.put(key, value); mSdkInfo.clear();
} }
public void addVersionInfo(String key, String value) { public void addSdkInfo(String key, String value) {
mVersionInfo.put(key, value); mSdkInfo.put(key, value);
} }
public void addVersionInfo(String key, String value) {
mVersionInfo.put(key, value);
}
public void addUnknownFileInfo(String file, String value) { public void addUnknownFileInfo(String file, String value) {
mUnknownFiles.put(file,value); mUnknownFiles.put(file,value);
} }
public Map<String, String> getVersionInfo() { public Map<String, String> getVersionInfo() {
return mVersionInfo; return mVersionInfo;
} }
public Map<String, String> getSdkInfo() { public Map<String, String> getSdkInfo() {
return mSdkInfo; return mSdkInfo;
} }
public boolean getAnalysisMode() { public boolean getAnalysisMode() {
return mAnalysisMode; return mAnalysisMode;

View File

@ -24,44 +24,44 @@ import java.util.*;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public final class ResType { public final class ResType {
private final String mName; private final String mName;
private final Map<String, ResResSpec> mResSpecs = new LinkedHashMap<String, ResResSpec>(); private final Map<String, ResResSpec> mResSpecs = new LinkedHashMap<String, ResResSpec>();
private final ResTable mResTable; private final ResTable mResTable;
private final ResPackage mPackage; private final ResPackage mPackage;
public ResType(String name, ResTable resTable, ResPackage package_) { public ResType(String name, ResTable resTable, ResPackage package_) {
this.mName = name; this.mName = name;
this.mResTable = resTable; this.mResTable = resTable;
this.mPackage = package_; this.mPackage = package_;
} }
public String getName() { public String getName() {
return mName; return mName;
} }
public Set<ResResSpec> listResSpecs() { public Set<ResResSpec> listResSpecs() {
return new LinkedHashSet<ResResSpec>(mResSpecs.values()); return new LinkedHashSet<ResResSpec>(mResSpecs.values());
} }
public ResResSpec getResSpec(String name) throws AndrolibException { public ResResSpec getResSpec(String name) throws AndrolibException {
ResResSpec spec = mResSpecs.get(name); ResResSpec spec = mResSpecs.get(name);
if (spec == null) { if (spec == null) {
throw new UndefinedResObject(String.format("resource spec: %s/%s", throw new UndefinedResObject(String.format("resource spec: %s/%s",
getName(), name)); getName(), name));
} }
return spec; return spec;
} }
public void addResSpec(ResResSpec spec) throws AndrolibException { public void addResSpec(ResResSpec spec) throws AndrolibException {
if (mResSpecs.put(spec.getName(), spec) != null) { if (mResSpecs.put(spec.getName(), spec) != null) {
throw new AndrolibException(String.format( throw new AndrolibException(String.format(
"Multiple res specs: %s/%s", getName(), spec.getName())); "Multiple res specs: %s/%s", getName(), spec.getName()));
} }
} }
@Override @Override
public String toString() { public String toString() {
return mName; return mName;
} }
} }

View File

@ -23,68 +23,68 @@ import java.util.Set;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public class ResValuesFile { public class ResValuesFile {
private final ResPackage mPackage; private final ResPackage mPackage;
private final ResType mType; private final ResType mType;
private final ResConfig mConfig; private final ResConfig mConfig;
private final Set<ResResource> mResources = new LinkedHashSet<ResResource>(); private final Set<ResResource> mResources = new LinkedHashSet<ResResource>();
public ResValuesFile(ResPackage pkg, ResType type, ResConfig config) { public ResValuesFile(ResPackage pkg, ResType type, ResConfig config) {
this.mPackage = pkg; this.mPackage = pkg;
this.mType = type; this.mType = type;
this.mConfig = config; this.mConfig = config;
} }
public String getPath() { public String getPath() {
return "values" + mConfig.getFlags().getQualifiers() + "/" return "values" + mConfig.getFlags().getQualifiers() + "/"
+ mType.getName() + (mType.getName().endsWith("s") ? "" : "s") + mType.getName() + (mType.getName().endsWith("s") ? "" : "s")
+ ".xml"; + ".xml";
} }
public Set<ResResource> listResources() { public Set<ResResource> listResources() {
return mResources; return mResources;
} }
public ResType getType() { public ResType getType() {
return mType; return mType;
} }
public ResConfig getConfig() { public ResConfig getConfig() {
return mConfig; return mConfig;
} }
public boolean isSynthesized(ResResource res) { public boolean isSynthesized(ResResource res) {
return mPackage.isSynthesized(res.getResSpec().getId()); return mPackage.isSynthesized(res.getResSpec().getId());
} }
public void addResource(ResResource res) { public void addResource(ResResource res) {
mResources.add(res); mResources.add(res);
} }
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (obj == null) { if (obj == null) {
return false; return false;
} }
if (getClass() != obj.getClass()) { if (getClass() != obj.getClass()) {
return false; return false;
} }
final ResValuesFile other = (ResValuesFile) obj; final ResValuesFile other = (ResValuesFile) obj;
if (this.mType != other.mType if (this.mType != other.mType
&& (this.mType == null || !this.mType.equals(other.mType))) { && (this.mType == null || !this.mType.equals(other.mType))) {
return false; return false;
} }
if (this.mConfig != other.mConfig if (this.mConfig != other.mConfig
&& (this.mConfig == null || !this.mConfig.equals(other.mConfig))) { && (this.mConfig == null || !this.mConfig.equals(other.mConfig))) {
return false; return false;
} }
return true; return true;
} }
@Override @Override
public int hashCode() { public int hashCode() {
int hash = 17; int hash = 17;
hash = 31 * hash + (this.mType != null ? this.mType.hashCode() : 0); hash = 31 * hash + (this.mType != null ? this.mType.hashCode() : 0);
hash = 31 * hash + (this.mConfig != null ? this.mConfig.hashCode() : 0); hash = 31 * hash + (this.mConfig != null ? this.mConfig.hashCode() : 0);
return hash; return hash;
} }
} }

View File

@ -28,148 +28,148 @@ import org.xmlpull.v1.XmlSerializer;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public class ResAttr extends ResBagValue implements ResValuesXmlSerializable { public class ResAttr extends ResBagValue implements ResValuesXmlSerializable {
ResAttr(ResReferenceValue parentVal, int type, Integer min, Integer max, ResAttr(ResReferenceValue parentVal, int type, Integer min, Integer max,
Boolean l10n) { Boolean l10n) {
super(parentVal); super(parentVal);
mType = type; mType = type;
mMin = min; mMin = min;
mMax = max; mMax = max;
mL10n = l10n; mL10n = l10n;
} }
public String convertToResXmlFormat(ResScalarValue value) public String convertToResXmlFormat(ResScalarValue value)
throws AndrolibException { throws AndrolibException {
return null; return null;
} }
@Override @Override
public void serializeToResValuesXml(XmlSerializer serializer, public void serializeToResValuesXml(XmlSerializer serializer,
ResResource res) throws IOException, AndrolibException { ResResource res) throws IOException, AndrolibException {
String type = getTypeAsString(); String type = getTypeAsString();
serializer.startTag(null, "attr"); serializer.startTag(null, "attr");
serializer.attribute(null, "name", res.getResSpec().getName()); serializer.attribute(null, "name", res.getResSpec().getName());
if (type != null) { if (type != null) {
serializer.attribute(null, "format", type); serializer.attribute(null, "format", type);
} }
if (mMin != null) { if (mMin != null) {
serializer.attribute(null, "min", mMin.toString()); serializer.attribute(null, "min", mMin.toString());
} }
if (mMax != null) { if (mMax != null) {
serializer.attribute(null, "max", mMax.toString()); serializer.attribute(null, "max", mMax.toString());
} }
if (mL10n != null && mL10n) { if (mL10n != null && mL10n) {
serializer.attribute(null, "localization", "suggested"); serializer.attribute(null, "localization", "suggested");
} }
serializeBody(serializer, res); serializeBody(serializer, res);
serializer.endTag(null, "attr"); serializer.endTag(null, "attr");
} }
public static ResAttr factory(ResReferenceValue parent, public static ResAttr factory(ResReferenceValue parent,
Duo<Integer, ResScalarValue>[] items, ResValueFactory factory, Duo<Integer, ResScalarValue>[] items, ResValueFactory factory,
ResPackage pkg) throws AndrolibException { ResPackage pkg) throws AndrolibException {
int type = ((ResIntValue) items[0].m2).getValue(); int type = ((ResIntValue) items[0].m2).getValue();
int scalarType = type & 0xffff; int scalarType = type & 0xffff;
Integer min = null, max = null; Integer min = null, max = null;
Boolean l10n = null; Boolean l10n = null;
int i; int i;
for (i = 1; i < items.length; i++) { for (i = 1; i < items.length; i++) {
switch (items[i].m1) { switch (items[i].m1) {
case BAG_KEY_ATTR_MIN: case BAG_KEY_ATTR_MIN:
min = ((ResIntValue) items[i].m2).getValue(); min = ((ResIntValue) items[i].m2).getValue();
continue; continue;
case BAG_KEY_ATTR_MAX: case BAG_KEY_ATTR_MAX:
max = ((ResIntValue) items[i].m2).getValue(); max = ((ResIntValue) items[i].m2).getValue();
continue; continue;
case BAG_KEY_ATTR_L10N: case BAG_KEY_ATTR_L10N:
l10n = ((ResIntValue) items[i].m2).getValue() != 0; l10n = ((ResIntValue) items[i].m2).getValue() != 0;
continue; continue;
} }
break; break;
} }
if (i == items.length) { if (i == items.length) {
return new ResAttr(parent, scalarType, min, max, l10n); return new ResAttr(parent, scalarType, min, max, l10n);
} }
Duo<ResReferenceValue, ResIntValue>[] attrItems = new Duo[items.length Duo<ResReferenceValue, ResIntValue>[] attrItems = new Duo[items.length
- i]; - i];
int j = 0; int j = 0;
for (; i < items.length; i++) { for (; i < items.length; i++) {
int resId = items[i].m1; int resId = items[i].m1;
pkg.addSynthesizedRes(resId); pkg.addSynthesizedRes(resId);
attrItems[j++] = new Duo<ResReferenceValue, ResIntValue>( attrItems[j++] = new Duo<ResReferenceValue, ResIntValue>(
factory.newReference(resId, null), factory.newReference(resId, null),
(ResIntValue) items[i].m2); (ResIntValue) items[i].m2);
} }
switch (type & 0xff0000) { switch (type & 0xff0000) {
case TYPE_ENUM: case TYPE_ENUM:
return new ResEnumAttr(parent, scalarType, min, max, l10n, return new ResEnumAttr(parent, scalarType, min, max, l10n,
attrItems); attrItems);
case TYPE_FLAGS: case TYPE_FLAGS:
return new ResFlagsAttr(parent, scalarType, min, max, l10n, return new ResFlagsAttr(parent, scalarType, min, max, l10n,
attrItems); attrItems);
} }
throw new AndrolibException("Could not decode attr value"); throw new AndrolibException("Could not decode attr value");
} }
protected void serializeBody(XmlSerializer serializer, ResResource res) protected void serializeBody(XmlSerializer serializer, ResResource res)
throws AndrolibException, IOException { throws AndrolibException, IOException {
} }
protected String getTypeAsString() { protected String getTypeAsString() {
String s = ""; String s = "";
if ((mType & TYPE_REFERENCE) != 0) { if ((mType & TYPE_REFERENCE) != 0) {
s += "|reference"; s += "|reference";
} }
if ((mType & TYPE_STRING) != 0) { if ((mType & TYPE_STRING) != 0) {
s += "|string"; s += "|string";
} }
if ((mType & TYPE_INT) != 0) { if ((mType & TYPE_INT) != 0) {
s += "|integer"; s += "|integer";
} }
if ((mType & TYPE_BOOL) != 0) { if ((mType & TYPE_BOOL) != 0) {
s += "|boolean"; s += "|boolean";
} }
if ((mType & TYPE_COLOR) != 0) { if ((mType & TYPE_COLOR) != 0) {
s += "|color"; s += "|color";
} }
if ((mType & TYPE_FLOAT) != 0) { if ((mType & TYPE_FLOAT) != 0) {
s += "|float"; s += "|float";
} }
if ((mType & TYPE_DIMEN) != 0) { if ((mType & TYPE_DIMEN) != 0) {
s += "|dimension"; s += "|dimension";
} }
if ((mType & TYPE_FRACTION) != 0) { if ((mType & TYPE_FRACTION) != 0) {
s += "|fraction"; s += "|fraction";
} }
if (s.isEmpty()) { if (s.isEmpty()) {
return null; return null;
} }
return s.substring(1); return s.substring(1);
} }
private final int mType; private final int mType;
private final Integer mMin; private final Integer mMin;
private final Integer mMax; private final Integer mMax;
private final Boolean mL10n; private final Boolean mL10n;
public static final int BAG_KEY_ATTR_TYPE = 0x01000000; public static final int BAG_KEY_ATTR_TYPE = 0x01000000;
private static final int BAG_KEY_ATTR_MIN = 0x01000001; private static final int BAG_KEY_ATTR_MIN = 0x01000001;
private static final int BAG_KEY_ATTR_MAX = 0x01000002; private static final int BAG_KEY_ATTR_MAX = 0x01000002;
private static final int BAG_KEY_ATTR_L10N = 0x01000003; private static final int BAG_KEY_ATTR_L10N = 0x01000003;
private final static int TYPE_REFERENCE = 0x01; private final static int TYPE_REFERENCE = 0x01;
private final static int TYPE_STRING = 0x02; private final static int TYPE_STRING = 0x02;
private final static int TYPE_INT = 0x04; private final static int TYPE_INT = 0x04;
private final static int TYPE_BOOL = 0x08; private final static int TYPE_BOOL = 0x08;
private final static int TYPE_COLOR = 0x10; private final static int TYPE_COLOR = 0x10;
private final static int TYPE_FLOAT = 0x20; private final static int TYPE_FLOAT = 0x20;
private final static int TYPE_DIMEN = 0x40; private final static int TYPE_DIMEN = 0x40;
private final static int TYPE_FRACTION = 0x80; private final static int TYPE_FRACTION = 0x80;
private final static int TYPE_ANY_STRING = 0xee; private final static int TYPE_ANY_STRING = 0xee;
private static final int TYPE_ENUM = 0x00010000; private static final int TYPE_ENUM = 0x00010000;
private static final int TYPE_FLAGS = 0x00020000; private static final int TYPE_FLAGS = 0x00020000;
} }

View File

@ -27,39 +27,39 @@ import org.xmlpull.v1.XmlSerializer;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public class ResBagValue extends ResValue implements ResValuesXmlSerializable { public class ResBagValue extends ResValue implements ResValuesXmlSerializable {
protected final ResReferenceValue mParent; protected final ResReferenceValue mParent;
public ResBagValue(ResReferenceValue parent) { public ResBagValue(ResReferenceValue parent) {
this.mParent = parent; this.mParent = parent;
} }
@Override @Override
public void serializeToResValuesXml(XmlSerializer serializer, public void serializeToResValuesXml(XmlSerializer serializer,
ResResource res) throws IOException, AndrolibException { ResResource res) throws IOException, AndrolibException {
String type = res.getResSpec().getType().getName(); String type = res.getResSpec().getType().getName();
if ("style".equals(type)) { if ("style".equals(type)) {
new ResStyleValue(mParent, new Duo[0], null) new ResStyleValue(mParent, new Duo[0], null)
.serializeToResValuesXml(serializer, res); .serializeToResValuesXml(serializer, res);
return; return;
} }
if ("array".equals(type)) { if ("array".equals(type)) {
new ResArrayValue(mParent, new Duo[0]).serializeToResValuesXml( new ResArrayValue(mParent, new Duo[0]).serializeToResValuesXml(
serializer, res); serializer, res);
return; return;
} }
if ("plurals".equals(type)) { if ("plurals".equals(type)) {
new ResPluralsValue(mParent, new Duo[0]).serializeToResValuesXml( new ResPluralsValue(mParent, new Duo[0]).serializeToResValuesXml(
serializer, res); serializer, res);
return; return;
} }
serializer.startTag(null, "item"); serializer.startTag(null, "item");
serializer.attribute(null, "type", type); serializer.attribute(null, "type", type);
serializer.attribute(null, "name", res.getResSpec().getName()); serializer.attribute(null, "name", res.getResSpec().getName());
serializer.endTag(null, "item"); serializer.endTag(null, "item");
} }
public ResReferenceValue getParent() { public ResReferenceValue getParent() {
return mParent; return mParent;
} }
} }

View File

@ -20,19 +20,19 @@ package brut.androlib.res.data.value;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public class ResBoolValue extends ResScalarValue { public class ResBoolValue extends ResScalarValue {
private final boolean mValue; private final boolean mValue;
public ResBoolValue(boolean value, String rawValue) { public ResBoolValue(boolean value, String rawValue) {
super("bool", rawValue); super("bool", rawValue);
this.mValue = value; this.mValue = value;
} }
public boolean getValue() { public boolean getValue() {
return mValue; return mValue;
} }
@Override @Override
protected String encodeAsResXml() { protected String encodeAsResXml() {
return mValue ? "true" : "false"; return mValue ? "true" : "false";
} }
} }

View File

@ -20,12 +20,12 @@ package brut.androlib.res.data.value;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public class ResColorValue extends ResIntValue { public class ResColorValue extends ResIntValue {
public ResColorValue(int value, String rawValue) { public ResColorValue(int value, String rawValue) {
super(value, rawValue, "color"); super(value, rawValue, "color");
} }
@Override @Override
protected String encodeAsResXml() { protected String encodeAsResXml() {
return String.format("#%08x", mValue); return String.format("#%08x", mValue);
} }
} }

View File

@ -23,12 +23,12 @@ import brut.androlib.AndrolibException;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public class ResDimenValue extends ResIntValue { public class ResDimenValue extends ResIntValue {
public ResDimenValue(int value, String rawValue) { public ResDimenValue(int value, String rawValue) {
super(value, rawValue, "dimen"); super(value, rawValue, "dimen");
} }
@Override @Override
protected String encodeAsResXml() throws AndrolibException { protected String encodeAsResXml() throws AndrolibException {
return TypedValue.coerceToString(TypedValue.TYPE_DIMENSION, mValue); return TypedValue.coerceToString(TypedValue.TYPE_DIMENSION, mValue);
} }
} }

View File

@ -28,55 +28,55 @@ import org.xmlpull.v1.XmlSerializer;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public class ResEnumAttr extends ResAttr { public class ResEnumAttr extends ResAttr {
ResEnumAttr(ResReferenceValue parent, int type, Integer min, Integer max, ResEnumAttr(ResReferenceValue parent, int type, Integer min, Integer max,
Boolean l10n, Duo<ResReferenceValue, ResIntValue>[] items) { Boolean l10n, Duo<ResReferenceValue, ResIntValue>[] items) {
super(parent, type, min, max, l10n); super(parent, type, min, max, l10n);
mItems = items; mItems = items;
} }
@Override @Override
public String convertToResXmlFormat(ResScalarValue value) public String convertToResXmlFormat(ResScalarValue value)
throws AndrolibException { throws AndrolibException {
if (value instanceof ResIntValue) { if (value instanceof ResIntValue) {
String ret = decodeValue(((ResIntValue) value).getValue()); String ret = decodeValue(((ResIntValue) value).getValue());
if (ret != null) { if (ret != null) {
return ret; return ret;
} }
} }
return super.convertToResXmlFormat(value); return super.convertToResXmlFormat(value);
} }
@Override @Override
protected void serializeBody(XmlSerializer serializer, ResResource res) protected void serializeBody(XmlSerializer serializer, ResResource res)
throws AndrolibException, IOException { throws AndrolibException, IOException {
for (Duo<ResReferenceValue, ResIntValue> duo : mItems) { for (Duo<ResReferenceValue, ResIntValue> duo : mItems) {
int intVal = duo.m2.getValue(); int intVal = duo.m2.getValue();
serializer.startTag(null, "enum"); serializer.startTag(null, "enum");
serializer.attribute(null, "name", duo.m1.getReferent().getName()); serializer.attribute(null, "name", duo.m1.getReferent().getName());
serializer.attribute(null, "value", String.valueOf(intVal)); serializer.attribute(null, "value", String.valueOf(intVal));
serializer.endTag(null, "enum"); serializer.endTag(null, "enum");
} }
} }
private String decodeValue(int value) throws AndrolibException { private String decodeValue(int value) throws AndrolibException {
String value2 = mItemsCache.get(value); String value2 = mItemsCache.get(value);
if (value2 == null) { if (value2 == null) {
ResReferenceValue ref = null; ResReferenceValue ref = null;
for (Duo<ResReferenceValue, ResIntValue> duo : mItems) { for (Duo<ResReferenceValue, ResIntValue> duo : mItems) {
if (duo.m2.getValue() == value) { if (duo.m2.getValue() == value) {
ref = duo.m1; ref = duo.m1;
break; break;
} }
} }
if (ref != null) { if (ref != null) {
value2 = ref.getReferent().getName(); value2 = ref.getReferent().getName();
mItemsCache.put(value, value2); mItemsCache.put(value, value2);
} }
} }
return value2; return value2;
} }
private final Duo<ResReferenceValue, ResIntValue>[] mItems; private final Duo<ResReferenceValue, ResIntValue>[] mItems;
private final Map<Integer, String> mItemsCache = new HashMap<Integer, String>(); private final Map<Integer, String> mItemsCache = new HashMap<Integer, String>();
} }

View File

@ -22,21 +22,21 @@ import brut.androlib.AndrolibException;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public class ResFileValue extends ResValue { public class ResFileValue extends ResValue {
private final String mPath; private final String mPath;
public ResFileValue(String path) { public ResFileValue(String path) {
this.mPath = path; this.mPath = path;
} }
public String getPath() { public String getPath() {
return mPath; return mPath;
} }
public String getStrippedPath() throws AndrolibException { public String getStrippedPath() throws AndrolibException {
if (!mPath.startsWith("res/")) { if (!mPath.startsWith("res/")) {
throw new AndrolibException( throw new AndrolibException(
"File path does not start with \"res/\": " + mPath); "File path does not start with \"res/\": " + mPath);
} }
return mPath.substring(4); return mPath.substring(4);
} }
} }

View File

@ -28,133 +28,133 @@ import org.xmlpull.v1.XmlSerializer;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public class ResFlagsAttr extends ResAttr { public class ResFlagsAttr extends ResAttr {
ResFlagsAttr(ResReferenceValue parent, int type, Integer min, Integer max, ResFlagsAttr(ResReferenceValue parent, int type, Integer min, Integer max,
Boolean l10n, Duo<ResReferenceValue, ResIntValue>[] items) { Boolean l10n, Duo<ResReferenceValue, ResIntValue>[] items) {
super(parent, type, min, max, l10n); super(parent, type, min, max, l10n);
mItems = new FlagItem[items.length]; mItems = new FlagItem[items.length];
for (int i = 0; i < items.length; i++) { for (int i = 0; i < items.length; i++) {
mItems[i] = new FlagItem(items[i].m1, items[i].m2.getValue()); mItems[i] = new FlagItem(items[i].m1, items[i].m2.getValue());
} }
} }
@Override @Override
public String convertToResXmlFormat(ResScalarValue value) public String convertToResXmlFormat(ResScalarValue value)
throws AndrolibException { throws AndrolibException {
if (!(value instanceof ResIntValue)) { if (!(value instanceof ResIntValue)) {
return super.convertToResXmlFormat(value); return super.convertToResXmlFormat(value);
} }
loadFlags(); loadFlags();
int intVal = ((ResIntValue) value).getValue(); int intVal = ((ResIntValue) value).getValue();
if (intVal == 0) { if (intVal == 0) {
return renderFlags(mZeroFlags); return renderFlags(mZeroFlags);
} }
FlagItem[] flagItems = new FlagItem[mFlags.length]; FlagItem[] flagItems = new FlagItem[mFlags.length];
int[] flags = new int[mFlags.length]; int[] flags = new int[mFlags.length];
int flagsCount = 0; int flagsCount = 0;
for (int i = 0; i < mFlags.length; i++) { for (int i = 0; i < mFlags.length; i++) {
FlagItem flagItem = mFlags[i]; FlagItem flagItem = mFlags[i];
int flag = flagItem.flag; int flag = flagItem.flag;
if ((intVal & flag) != flag) { if ((intVal & flag) != flag) {
continue; continue;
} }
if (!isSubpartOf(flag, flags)) { if (!isSubpartOf(flag, flags)) {
flags[flagsCount] = flag; flags[flagsCount] = flag;
flagItems[flagsCount++] = flagItem; flagItems[flagsCount++] = flagItem;
} }
} }
return renderFlags(Arrays.copyOf(flagItems, flagsCount)); return renderFlags(Arrays.copyOf(flagItems, flagsCount));
} }
@Override @Override
protected void serializeBody(XmlSerializer serializer, ResResource res) protected void serializeBody(XmlSerializer serializer, ResResource res)
throws AndrolibException, IOException { throws AndrolibException, IOException {
for (int i = 0; i < mItems.length; i++) { for (int i = 0; i < mItems.length; i++) {
FlagItem item = mItems[i]; FlagItem item = mItems[i];
serializer.startTag(null, "flag"); serializer.startTag(null, "flag");
serializer.attribute(null, "name", item.getValue()); serializer.attribute(null, "name", item.getValue());
serializer.attribute(null, "value", serializer.attribute(null, "value",
String.format("0x%08x", item.flag)); String.format("0x%08x", item.flag));
serializer.endTag(null, "flag"); serializer.endTag(null, "flag");
} }
} }
private boolean isSubpartOf(int flag, int[] flags) { private boolean isSubpartOf(int flag, int[] flags) {
for (int i = 0; i < flags.length; i++) { for (int i = 0; i < flags.length; i++) {
if ((flags[i] & flag) == flag) { if ((flags[i] & flag) == flag) {
return true; return true;
} }
} }
return false; return false;
} }
private String renderFlags(FlagItem[] flags) throws AndrolibException { private String renderFlags(FlagItem[] flags) throws AndrolibException {
String ret = ""; String ret = "";
for (int i = 0; i < flags.length; i++) { for (int i = 0; i < flags.length; i++) {
ret += "|" + flags[i].getValue(); ret += "|" + flags[i].getValue();
} }
if (ret.isEmpty()) { if (ret.isEmpty()) {
return ret; return ret;
} }
return ret.substring(1); return ret.substring(1);
} }
private void loadFlags() { private void loadFlags() {
if (mFlags != null) { if (mFlags != null) {
return; return;
} }
FlagItem[] zeroFlags = new FlagItem[mItems.length]; FlagItem[] zeroFlags = new FlagItem[mItems.length];
int zeroFlagsCount = 0; int zeroFlagsCount = 0;
FlagItem[] flags = new FlagItem[mItems.length]; FlagItem[] flags = new FlagItem[mItems.length];
int flagsCount = 0; int flagsCount = 0;
for (int i = 0; i < mItems.length; i++) { for (int i = 0; i < mItems.length; i++) {
FlagItem item = mItems[i]; FlagItem item = mItems[i];
if (item.flag == 0) { if (item.flag == 0) {
zeroFlags[zeroFlagsCount++] = item; zeroFlags[zeroFlagsCount++] = item;
} else { } else {
flags[flagsCount++] = item; flags[flagsCount++] = item;
} }
} }
mZeroFlags = Arrays.copyOf(zeroFlags, zeroFlagsCount); mZeroFlags = Arrays.copyOf(zeroFlags, zeroFlagsCount);
mFlags = Arrays.copyOf(flags, flagsCount); mFlags = Arrays.copyOf(flags, flagsCount);
Arrays.sort(mFlags, new Comparator<FlagItem>() { Arrays.sort(mFlags, new Comparator<FlagItem>() {
@Override @Override
public int compare(FlagItem o1, FlagItem o2) { public int compare(FlagItem o1, FlagItem o2) {
return Integer.valueOf(Integer.bitCount(o2.flag)).compareTo( return Integer.valueOf(Integer.bitCount(o2.flag)).compareTo(
Integer.bitCount(o1.flag)); Integer.bitCount(o1.flag));
} }
}); });
} }
private final FlagItem[] mItems; private final FlagItem[] mItems;
private FlagItem[] mZeroFlags; private FlagItem[] mZeroFlags;
private FlagItem[] mFlags; private FlagItem[] mFlags;
private static class FlagItem { private static class FlagItem {
public final ResReferenceValue ref; public final ResReferenceValue ref;
public final int flag; public final int flag;
public String value; public String value;
public FlagItem(ResReferenceValue ref, int flag) { public FlagItem(ResReferenceValue ref, int flag) {
this.ref = ref; this.ref = ref;
this.flag = flag; this.flag = flag;
} }
public String getValue() throws AndrolibException { public String getValue() throws AndrolibException {
if (value == null) { if (value == null) {
value = ref.getReferent().getName(); value = ref.getReferent().getName();
} }
return value; return value;
} }
} }
} }

View File

@ -20,19 +20,19 @@ package brut.androlib.res.data.value;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public class ResFloatValue extends ResScalarValue { public class ResFloatValue extends ResScalarValue {
private final float mValue; private final float mValue;
public ResFloatValue(float value, String rawValue) { public ResFloatValue(float value, String rawValue) {
super("float", rawValue); super("float", rawValue);
this.mValue = value; this.mValue = value;
} }
public float getValue() { public float getValue() {
return mValue; return mValue;
} }
@Override @Override
protected String encodeAsResXml() { protected String encodeAsResXml() {
return String.valueOf(mValue); return String.valueOf(mValue);
} }
} }

View File

@ -23,12 +23,12 @@ import brut.androlib.AndrolibException;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public class ResFractionValue extends ResIntValue { public class ResFractionValue extends ResIntValue {
public ResFractionValue(int value, String rawValue) { public ResFractionValue(int value, String rawValue) {
super(value, rawValue, "fraction"); super(value, rawValue, "fraction");
} }
@Override @Override
protected String encodeAsResXml() throws AndrolibException { protected String encodeAsResXml() throws AndrolibException {
return TypedValue.coerceToString(TypedValue.TYPE_FRACTION, mValue); return TypedValue.coerceToString(TypedValue.TYPE_FRACTION, mValue);
} }
} }

View File

@ -26,13 +26,13 @@ import org.xmlpull.v1.XmlSerializer;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public class ResIdValue extends ResValue implements ResValuesXmlSerializable { public class ResIdValue extends ResValue implements ResValuesXmlSerializable {
@Override @Override
public void serializeToResValuesXml(XmlSerializer serializer, public void serializeToResValuesXml(XmlSerializer serializer,
ResResource res) throws IOException, AndrolibException { ResResource res) throws IOException, AndrolibException {
serializer.startTag(null, "item"); serializer.startTag(null, "item");
serializer serializer
.attribute(null, "type", res.getResSpec().getType().getName()); .attribute(null, "type", res.getResSpec().getType().getName());
serializer.attribute(null, "name", res.getResSpec().getName()); serializer.attribute(null, "name", res.getResSpec().getName());
serializer.endTag(null, "item"); serializer.endTag(null, "item");
} }
} }

View File

@ -23,25 +23,25 @@ import brut.androlib.AndrolibException;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public class ResIntValue extends ResScalarValue { public class ResIntValue extends ResScalarValue {
protected final int mValue; protected final int mValue;
private int type; private int type;
public ResIntValue(int value, String rawValue, int type) { public ResIntValue(int value, String rawValue, int type) {
this(value, rawValue, "integer"); this(value, rawValue, "integer");
this.type = type; this.type = type;
} }
public ResIntValue(int value, String rawValue, String type) { public ResIntValue(int value, String rawValue, String type) {
super(type, rawValue); super(type, rawValue);
this.mValue = value; this.mValue = value;
} }
public int getValue() { public int getValue() {
return mValue; return mValue;
} }
@Override @Override
protected String encodeAsResXml() throws AndrolibException { protected String encodeAsResXml() throws AndrolibException {
return TypedValue.coerceToString(type, mValue); return TypedValue.coerceToString(type, mValue);
} }
} }

View File

@ -29,42 +29,42 @@ import org.xmlpull.v1.XmlSerializer;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public class ResPluralsValue extends ResBagValue implements public class ResPluralsValue extends ResBagValue implements
ResValuesXmlSerializable { ResValuesXmlSerializable {
ResPluralsValue(ResReferenceValue parent, ResPluralsValue(ResReferenceValue parent,
Duo<Integer, ResScalarValue>[] items) { Duo<Integer, ResScalarValue>[] items) {
super(parent); super(parent);
mItems = new ResScalarValue[6]; mItems = new ResScalarValue[6];
for (int i = 0; i < items.length; i++) { for (int i = 0; i < items.length; i++) {
mItems[items[i].m1 - BAG_KEY_PLURALS_START] = items[i].m2; mItems[items[i].m1 - BAG_KEY_PLURALS_START] = items[i].m2;
} }
} }
@Override @Override
public void serializeToResValuesXml(XmlSerializer serializer, public void serializeToResValuesXml(XmlSerializer serializer,
ResResource res) throws IOException, AndrolibException { ResResource res) throws IOException, AndrolibException {
serializer.startTag(null, "plurals"); serializer.startTag(null, "plurals");
serializer.attribute(null, "name", res.getResSpec().getName()); serializer.attribute(null, "name", res.getResSpec().getName());
for (int i = 0; i < mItems.length; i++) { for (int i = 0; i < mItems.length; i++) {
ResScalarValue item = mItems[i]; ResScalarValue item = mItems[i];
if (item == null) { if (item == null) {
continue; continue;
} }
ResScalarValue rawValue = item; ResScalarValue rawValue = item;
serializer.startTag(null, "item"); serializer.startTag(null, "item");
serializer.attribute(null, "quantity", QUANTITY_MAP[i]); serializer.attribute(null, "quantity", QUANTITY_MAP[i]);
serializer.text(ResXmlEncoders.enumerateNonPositionalSubstitutionsIfRequired(item.encodeAsResXmlValue())); serializer.text(ResXmlEncoders.enumerateNonPositionalSubstitutionsIfRequired(item.encodeAsResXmlValue()));
serializer.endTag(null, "item"); serializer.endTag(null, "item");
} }
serializer.endTag(null, "plurals"); serializer.endTag(null, "plurals");
} }
private final ResScalarValue[] mItems; private final ResScalarValue[] mItems;
public static final int BAG_KEY_PLURALS_START = 0x01000004; public static final int BAG_KEY_PLURALS_START = 0x01000004;
public static final int BAG_KEY_PLURALS_END = 0x01000009; public static final int BAG_KEY_PLURALS_END = 0x01000009;
private static final String[] QUANTITY_MAP = new String[] { "other", private static final String[] QUANTITY_MAP = new String[] { "other",
"zero", "one", "two", "few", "many" }; "zero", "one", "two", "few", "many" };
} }

View File

@ -24,43 +24,43 @@ import brut.androlib.res.data.ResResSpec;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public class ResReferenceValue extends ResIntValue { public class ResReferenceValue extends ResIntValue {
private final ResPackage mPackage; private final ResPackage mPackage;
private final boolean mTheme; private final boolean mTheme;
public ResReferenceValue(ResPackage package_, int value, String rawValue) { public ResReferenceValue(ResPackage package_, int value, String rawValue) {
this(package_, value, rawValue, false); this(package_, value, rawValue, false);
} }
public ResReferenceValue(ResPackage package_, int value, String rawValue, public ResReferenceValue(ResPackage package_, int value, String rawValue,
boolean theme) { boolean theme) {
super(value, rawValue, "reference"); super(value, rawValue, "reference");
mPackage = package_; mPackage = package_;
mTheme = theme; mTheme = theme;
} }
@Override @Override
protected String encodeAsResXml() throws AndrolibException { protected String encodeAsResXml() throws AndrolibException {
if (isNull()) { if (isNull()) {
return "@null"; return "@null";
} }
ResResSpec spec = getReferent(); ResResSpec spec = getReferent();
boolean newId = spec.hasDefaultResource() boolean newId = spec.hasDefaultResource()
&& spec.getDefaultResource().getValue() instanceof ResIdValue; && spec.getDefaultResource().getValue() instanceof ResIdValue;
// generate the beginning to fix @android // generate the beginning to fix @android
String mStart = (mTheme ? '?' : '@') + (newId ? "+" : ""); String mStart = (mTheme ? '?' : '@') + (newId ? "+" : "");
return mStart return mStart
+ spec.getFullName(mPackage, mTheme + spec.getFullName(mPackage, mTheme
&& spec.getType().getName().equals("attr")); && spec.getType().getName().equals("attr"));
} }
public ResResSpec getReferent() throws AndrolibException { public ResResSpec getReferent() throws AndrolibException {
return mPackage.getResTable().getResSpec(getValue()); return mPackage.getResTable().getResSpec(getValue());
} }
public boolean isNull() { public boolean isNull() {
return mValue == 0; return mValue == 0;
} }
} }

View File

@ -28,48 +28,48 @@ import org.xmlpull.v1.XmlSerializer;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public abstract class ResScalarValue extends ResValue implements public abstract class ResScalarValue extends ResValue implements
ResXmlEncodable, ResValuesXmlSerializable { ResXmlEncodable, ResValuesXmlSerializable {
protected final String mType; protected final String mType;
protected final String mRawValue; protected final String mRawValue;
protected ResScalarValue(String type, String rawValue) { protected ResScalarValue(String type, String rawValue) {
mType = type; mType = type;
mRawValue = rawValue; mRawValue = rawValue;
} }
@Override @Override
public String encodeAsResXmlAttr() throws AndrolibException { public String encodeAsResXmlAttr() throws AndrolibException {
if (mRawValue != null) { if (mRawValue != null) {
return mRawValue; return mRawValue;
} }
return encodeAsResXml(); return encodeAsResXml();
} }
public String encodeAsResXmlItemValue() throws AndrolibException { public String encodeAsResXmlItemValue() throws AndrolibException {
return encodeAsResXmlValue(); return encodeAsResXmlValue();
} }
@Override @Override
public String encodeAsResXmlValue() throws AndrolibException { public String encodeAsResXmlValue() throws AndrolibException {
if (mRawValue != null) { if (mRawValue != null) {
return mRawValue; return mRawValue;
} }
return encodeAsResXml(); return encodeAsResXml();
} }
public String encodeAsResXmlNonEscapedItemValue() throws AndrolibException { public String encodeAsResXmlNonEscapedItemValue() throws AndrolibException {
return encodeAsResXmlValue().replace("&amp;", "&").replace("&lt;","<"); return encodeAsResXmlValue().replace("&amp;", "&").replace("&lt;","<");
} }
@Override @Override
public void serializeToResValuesXml(XmlSerializer serializer, public void serializeToResValuesXml(XmlSerializer serializer,
ResResource res) throws IOException, AndrolibException { ResResource res) throws IOException, AndrolibException {
String type = res.getResSpec().getType().getName(); String type = res.getResSpec().getType().getName();
boolean item = !"reference".equals(mType) && !type.equals(mType); boolean item = !"reference".equals(mType) && !type.equals(mType);
String body = encodeAsResXmlValue(); String body = encodeAsResXmlValue();
// check for resource reference // check for resource reference
if (!type.equalsIgnoreCase("color")) { if (!type.equalsIgnoreCase("color")) {
if (body.contains("@")) { if (body.contains("@")) {
if (!res.getFilePath().contains("string")) { if (!res.getFilePath().contains("string")) {
@ -78,31 +78,31 @@ public abstract class ResScalarValue extends ResValue implements
} }
} }
// check for using attrib as node or item // check for using attrib as node or item
String tagName = item ? "item" : type; String tagName = item ? "item" : type;
serializer.startTag(null, tagName); serializer.startTag(null, tagName);
if (item) { if (item) {
serializer.attribute(null, "type", type); serializer.attribute(null, "type", type);
} }
serializer.attribute(null, "name", res.getResSpec().getName()); serializer.attribute(null, "name", res.getResSpec().getName());
serializeExtraXmlAttrs(serializer, res); serializeExtraXmlAttrs(serializer, res);
if (!body.isEmpty()) { if (!body.isEmpty()) {
serializer.ignorableWhitespace(body); serializer.ignorableWhitespace(body);
} }
serializer.endTag(null, tagName); serializer.endTag(null, tagName);
} }
public String getType() { public String getType() {
return mType; return mType;
} }
protected void serializeExtraXmlAttrs(XmlSerializer serializer, protected void serializeExtraXmlAttrs(XmlSerializer serializer,
ResResource res) throws IOException { ResResource res) throws IOException {
} }
protected abstract String encodeAsResXml() throws AndrolibException; protected abstract String encodeAsResXml() throws AndrolibException;
} }

View File

@ -27,41 +27,41 @@ import org.xmlpull.v1.XmlSerializer;
*/ */
public class ResStringValue extends ResScalarValue { public class ResStringValue extends ResScalarValue {
public ResStringValue(String value) { public ResStringValue(String value) {
this(value, "string"); this(value, "string");
} }
public ResStringValue(String value, String type) { public ResStringValue(String value, String type) {
super(type, value); super(type, value);
} }
@Override @Override
public String encodeAsResXmlAttr() { public String encodeAsResXmlAttr() {
return ResXmlEncoders.encodeAsResXmlAttr(mRawValue); return ResXmlEncoders.encodeAsResXmlAttr(mRawValue);
} }
@Override @Override
public String encodeAsResXmlItemValue() { public String encodeAsResXmlItemValue() {
return ResXmlEncoders return ResXmlEncoders
.enumerateNonPositionalSubstitutionsIfRequired(ResXmlEncoders .enumerateNonPositionalSubstitutionsIfRequired(ResXmlEncoders
.encodeAsXmlValue(mRawValue)); .encodeAsXmlValue(mRawValue));
} }
@Override @Override
public String encodeAsResXmlValue() { public String encodeAsResXmlValue() {
return ResXmlEncoders.encodeAsXmlValue(mRawValue); return ResXmlEncoders.encodeAsXmlValue(mRawValue);
} }
@Override @Override
protected String encodeAsResXml() throws AndrolibException { protected String encodeAsResXml() throws AndrolibException {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override @Override
protected void serializeExtraXmlAttrs(XmlSerializer serializer, protected void serializeExtraXmlAttrs(XmlSerializer serializer,
ResResource res) throws IOException { ResResource res) throws IOException {
if (ResXmlEncoders.hasMultipleNonPositionalSubstitutions(mRawValue)) { if (ResXmlEncoders.hasMultipleNonPositionalSubstitutions(mRawValue)) {
serializer.attribute(null, "formatted", "false"); serializer.attribute(null, "formatted", "false");
} }
} }
} }

View File

@ -28,55 +28,55 @@ import org.xmlpull.v1.XmlSerializer;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public class ResStyleValue extends ResBagValue implements public class ResStyleValue extends ResBagValue implements
ResValuesXmlSerializable { ResValuesXmlSerializable {
ResStyleValue(ResReferenceValue parent, ResStyleValue(ResReferenceValue parent,
Duo<Integer, ResScalarValue>[] items, ResValueFactory factory) { Duo<Integer, ResScalarValue>[] items, ResValueFactory factory) {
super(parent); super(parent);
mItems = new Duo[items.length]; mItems = new Duo[items.length];
for (int i = 0; i < items.length; i++) { for (int i = 0; i < items.length; i++) {
mItems[i] = new Duo<ResReferenceValue, ResScalarValue>( mItems[i] = new Duo<ResReferenceValue, ResScalarValue>(
factory.newReference(items[i].m1, null), items[i].m2); factory.newReference(items[i].m1, null), items[i].m2);
} }
} }
@Override @Override
public void serializeToResValuesXml(XmlSerializer serializer, public void serializeToResValuesXml(XmlSerializer serializer,
ResResource res) throws IOException, AndrolibException { ResResource res) throws IOException, AndrolibException {
serializer.startTag(null, "style"); serializer.startTag(null, "style");
serializer.attribute(null, "name", res.getResSpec().getName()); serializer.attribute(null, "name", res.getResSpec().getName());
if (!mParent.isNull()) { if (!mParent.isNull()) {
serializer.attribute(null, "parent", mParent.encodeAsResXmlAttr()); serializer.attribute(null, "parent", mParent.encodeAsResXmlAttr());
} else if (res.getResSpec().getName().indexOf('.') != -1) { } else if (res.getResSpec().getName().indexOf('.') != -1) {
serializer.attribute(null, "parent", ""); serializer.attribute(null, "parent", "");
} }
for (int i = 0; i < mItems.length; i++) { for (int i = 0; i < mItems.length; i++) {
ResResSpec spec = mItems[i].m1.getReferent(); ResResSpec spec = mItems[i].m1.getReferent();
// hacky-fix remove bad ReferenceVars // hacky-fix remove bad ReferenceVars
if (spec.getDefaultResource().getValue().toString() if (spec.getDefaultResource().getValue().toString()
.contains("ResReferenceValue@")) { .contains("ResReferenceValue@")) {
continue; continue;
} }
ResAttr attr = (ResAttr) spec.getDefaultResource().getValue(); ResAttr attr = (ResAttr) spec.getDefaultResource().getValue();
String value = attr.convertToResXmlFormat(mItems[i].m2); String value = attr.convertToResXmlFormat(mItems[i].m2);
if (value == null) { if (value == null) {
value = mItems[i].m2.encodeAsResXmlValue(); value = mItems[i].m2.encodeAsResXmlValue();
} }
if (value == null) { if (value == null) {
continue; continue;
} }
serializer.startTag(null, "item"); serializer.startTag(null, "item");
serializer.attribute(null, "name", serializer.attribute(null, "name",
spec.getFullName(res.getResSpec().getPackage(), true)); spec.getFullName(res.getResSpec().getPackage(), true));
serializer.text(value); serializer.text(value);
serializer.endTag(null, "item"); serializer.endTag(null, "item");
} }
serializer.endTag(null, "style"); serializer.endTag(null, "style");
} }
private final Duo<ResReferenceValue, ResScalarValue>[] mItems; private final Duo<ResReferenceValue, ResScalarValue>[] mItems;
} }

View File

@ -25,77 +25,77 @@ import brut.util.Duo;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public class ResValueFactory { public class ResValueFactory {
private final ResPackage mPackage; private final ResPackage mPackage;
public ResValueFactory(ResPackage pakage_) { public ResValueFactory(ResPackage pakage_) {
this.mPackage = pakage_; this.mPackage = pakage_;
} }
public ResScalarValue factory(int type, int value, String rawValue) public ResScalarValue factory(int type, int value, String rawValue)
throws AndrolibException { throws AndrolibException {
switch (type) { switch (type) {
case TypedValue.TYPE_REFERENCE: case TypedValue.TYPE_REFERENCE:
return newReference(value, rawValue); return newReference(value, rawValue);
case TypedValue.TYPE_ATTRIBUTE: case TypedValue.TYPE_ATTRIBUTE:
return newReference(value, rawValue, true); return newReference(value, rawValue, true);
case TypedValue.TYPE_STRING: case TypedValue.TYPE_STRING:
return new ResStringValue(rawValue); return new ResStringValue(rawValue);
case TypedValue.TYPE_FLOAT: case TypedValue.TYPE_FLOAT:
return new ResFloatValue(Float.intBitsToFloat(value), rawValue); return new ResFloatValue(Float.intBitsToFloat(value), rawValue);
case TypedValue.TYPE_DIMENSION: case TypedValue.TYPE_DIMENSION:
return new ResDimenValue(value, rawValue); return new ResDimenValue(value, rawValue);
case TypedValue.TYPE_FRACTION: case TypedValue.TYPE_FRACTION:
return new ResFractionValue(value, rawValue); return new ResFractionValue(value, rawValue);
case TypedValue.TYPE_INT_BOOLEAN: case TypedValue.TYPE_INT_BOOLEAN:
return new ResBoolValue(value != 0, rawValue); return new ResBoolValue(value != 0, rawValue);
} }
if (type >= TypedValue.TYPE_FIRST_COLOR_INT if (type >= TypedValue.TYPE_FIRST_COLOR_INT
&& type <= TypedValue.TYPE_LAST_COLOR_INT) { && type <= TypedValue.TYPE_LAST_COLOR_INT) {
return new ResColorValue(value, rawValue); return new ResColorValue(value, rawValue);
} }
if (type >= TypedValue.TYPE_FIRST_INT if (type >= TypedValue.TYPE_FIRST_INT
&& type <= TypedValue.TYPE_LAST_INT) { && type <= TypedValue.TYPE_LAST_INT) {
return new ResIntValue(value, rawValue, type); return new ResIntValue(value, rawValue, type);
} }
throw new AndrolibException("Invalid value type: " + type); throw new AndrolibException("Invalid value type: " + type);
} }
public ResValue factory(String value) { public ResValue factory(String value) {
if (value.startsWith("res/")) { if (value.startsWith("res/")) {
return new ResFileValue(value); return new ResFileValue(value);
} }
return new ResStringValue(value); return new ResStringValue(value);
} }
public ResBagValue bagFactory(int parent, public ResBagValue bagFactory(int parent,
Duo<Integer, ResScalarValue>[] items) throws AndrolibException { Duo<Integer, ResScalarValue>[] items) throws AndrolibException {
ResReferenceValue parentVal = newReference(parent, null); ResReferenceValue parentVal = newReference(parent, null);
if (items.length == 0) { if (items.length == 0) {
return new ResBagValue(parentVal); return new ResBagValue(parentVal);
} }
int key = items[0].m1; int key = items[0].m1;
if (key == ResAttr.BAG_KEY_ATTR_TYPE) { if (key == ResAttr.BAG_KEY_ATTR_TYPE) {
return ResAttr.factory(parentVal, items, this, mPackage); return ResAttr.factory(parentVal, items, this, mPackage);
} }
if (key == ResArrayValue.BAG_KEY_ARRAY_START) { if (key == ResArrayValue.BAG_KEY_ARRAY_START) {
return new ResArrayValue(parentVal, items); return new ResArrayValue(parentVal, items);
} }
if (key >= ResPluralsValue.BAG_KEY_PLURALS_START if (key >= ResPluralsValue.BAG_KEY_PLURALS_START
&& key <= ResPluralsValue.BAG_KEY_PLURALS_END) { && key <= ResPluralsValue.BAG_KEY_PLURALS_END) {
return new ResPluralsValue(parentVal, items); return new ResPluralsValue(parentVal, items);
} }
return new ResStyleValue(parentVal, items, this); return new ResStyleValue(parentVal, items, this);
} }
public ResReferenceValue newReference(int resID, String rawValue) { public ResReferenceValue newReference(int resID, String rawValue) {
return newReference(resID, rawValue, false); return newReference(resID, rawValue, false);
} }
public ResReferenceValue newReference(int resID, String rawValue, public ResReferenceValue newReference(int resID, String rawValue,
boolean theme) { boolean theme) {
return new ResReferenceValue(mPackage, resID, rawValue, theme); return new ResReferenceValue(mPackage, resID, rawValue, theme);
} }
} }

View File

@ -34,391 +34,391 @@ import org.apache.commons.io.input.CountingInputStream;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public class ARSCDecoder { public class ARSCDecoder {
public static ARSCData decode(InputStream arscStream, public static ARSCData decode(InputStream arscStream,
boolean findFlagsOffsets, boolean keepBroken) boolean findFlagsOffsets, boolean keepBroken)
throws AndrolibException { throws AndrolibException {
return decode(arscStream, findFlagsOffsets, keepBroken, new ResTable()); return decode(arscStream, findFlagsOffsets, keepBroken, new ResTable());
} }
public static ARSCData decode(InputStream arscStream, public static ARSCData decode(InputStream arscStream,
boolean findFlagsOffsets, boolean keepBroken, ResTable resTable) boolean findFlagsOffsets, boolean keepBroken, ResTable resTable)
throws AndrolibException { throws AndrolibException {
try { try {
ARSCDecoder decoder = new ARSCDecoder(arscStream, resTable, ARSCDecoder decoder = new ARSCDecoder(arscStream, resTable,
findFlagsOffsets, keepBroken); findFlagsOffsets, keepBroken);
ResPackage[] pkgs = decoder.readTable(); ResPackage[] pkgs = decoder.readTable();
return new ARSCData(pkgs, decoder.mFlagsOffsets == null ? null return new ARSCData(pkgs, decoder.mFlagsOffsets == null ? null
: decoder.mFlagsOffsets.toArray(new FlagsOffset[0]), : decoder.mFlagsOffsets.toArray(new FlagsOffset[0]),
resTable); resTable);
} catch (IOException ex) { } catch (IOException ex) {
throw new AndrolibException("Could not decode arsc file", ex); throw new AndrolibException("Could not decode arsc file", ex);
} }
} }
private ARSCDecoder(InputStream arscStream, ResTable resTable, private ARSCDecoder(InputStream arscStream, ResTable resTable,
boolean storeFlagsOffsets, boolean keepBroken) { boolean storeFlagsOffsets, boolean keepBroken) {
if (storeFlagsOffsets) { if (storeFlagsOffsets) {
arscStream = mCountIn = new CountingInputStream(arscStream); arscStream = mCountIn = new CountingInputStream(arscStream);
mFlagsOffsets = new ArrayList<FlagsOffset>(); mFlagsOffsets = new ArrayList<FlagsOffset>();
} else { } else {
mCountIn = null; mCountIn = null;
mFlagsOffsets = null; mFlagsOffsets = null;
} }
mIn = new ExtDataInput(new LEDataInputStream(arscStream)); mIn = new ExtDataInput(new LEDataInputStream(arscStream));
mResTable = resTable; mResTable = resTable;
mKeepBroken = keepBroken; mKeepBroken = keepBroken;
} }
private ResPackage[] readTable() throws IOException, AndrolibException { private ResPackage[] readTable() throws IOException, AndrolibException {
nextChunkCheckType(Header.TYPE_TABLE); nextChunkCheckType(Header.TYPE_TABLE);
int packageCount = mIn.readInt(); int packageCount = mIn.readInt();
mTableStrings = StringBlock.read(mIn); mTableStrings = StringBlock.read(mIn);
ResPackage[] packages = new ResPackage[packageCount]; ResPackage[] packages = new ResPackage[packageCount];
nextChunk(); nextChunk();
for (int i = 0; i < packageCount; i++) { for (int i = 0; i < packageCount; i++) {
packages[i] = readPackage(); packages[i] = readPackage();
} }
return packages; return packages;
} }
private ResPackage readPackage() throws IOException, AndrolibException { private ResPackage readPackage() throws IOException, AndrolibException {
checkChunkType(Header.TYPE_PACKAGE); checkChunkType(Header.TYPE_PACKAGE);
int id = (byte) mIn.readInt(); int id = (byte) mIn.readInt();
String name = mIn.readNulEndedString(128, true); String name = mIn.readNulEndedString(128, true);
/* typeNameStrings */mIn.skipInt(); /* typeNameStrings */mIn.skipInt();
/* typeNameCount */mIn.skipInt(); /* typeNameCount */mIn.skipInt();
/* specNameStrings */mIn.skipInt(); /* specNameStrings */mIn.skipInt();
/* specNameCount */mIn.skipInt(); /* specNameCount */mIn.skipInt();
mTypeNames = StringBlock.read(mIn); mTypeNames = StringBlock.read(mIn);
mSpecNames = StringBlock.read(mIn); mSpecNames = StringBlock.read(mIn);
mResId = id << 24; mResId = id << 24;
mPkg = new ResPackage(mResTable, id, name); mPkg = new ResPackage(mResTable, id, name);
nextChunk(); nextChunk();
while (mHeader.type == Header.TYPE_TYPE) { while (mHeader.type == Header.TYPE_TYPE) {
readType(); readType();
} }
return mPkg; return mPkg;
} }
private ResType readType() throws AndrolibException, IOException { private ResType readType() throws AndrolibException, IOException {
checkChunkType(Header.TYPE_TYPE); checkChunkType(Header.TYPE_TYPE);
byte id = mIn.readByte(); byte id = mIn.readByte();
mIn.skipBytes(3); mIn.skipBytes(3);
int entryCount = mIn.readInt(); int entryCount = mIn.readInt();
mMissingResSpecs = new boolean[entryCount]; mMissingResSpecs = new boolean[entryCount];
Arrays.fill(mMissingResSpecs, true); Arrays.fill(mMissingResSpecs, true);
if (mFlagsOffsets != null) { if (mFlagsOffsets != null) {
mFlagsOffsets.add(new FlagsOffset(mCountIn.getCount(), entryCount)); mFlagsOffsets.add(new FlagsOffset(mCountIn.getCount(), entryCount));
} }
/* flags */mIn.skipBytes(entryCount * 4); /* flags */mIn.skipBytes(entryCount * 4);
mResId = (0xff000000 & mResId) | id << 16; mResId = (0xff000000 & mResId) | id << 16;
mType = new ResType(mTypeNames.getString(id - 1), mResTable, mPkg); mType = new ResType(mTypeNames.getString(id - 1), mResTable, mPkg);
mPkg.addType(mType); mPkg.addType(mType);
while (nextChunk().type == Header.TYPE_CONFIG) { while (nextChunk().type == Header.TYPE_CONFIG) {
readConfig(); readConfig();
} }
addMissingResSpecs(); addMissingResSpecs();
return mType; return mType;
} }
private ResConfig readConfig() throws IOException, AndrolibException { private ResConfig readConfig() throws IOException, AndrolibException {
checkChunkType(Header.TYPE_CONFIG); checkChunkType(Header.TYPE_CONFIG);
/* typeId */mIn.skipInt(); /* typeId */mIn.skipInt();
int entryCount = mIn.readInt(); int entryCount = mIn.readInt();
/* entriesStart */mIn.skipInt(); /* entriesStart */mIn.skipInt();
ResConfigFlags flags = readConfigFlags(); ResConfigFlags flags = readConfigFlags();
int[] entryOffsets = mIn.readIntArray(entryCount); int[] entryOffsets = mIn.readIntArray(entryCount);
if (flags.isInvalid) { if (flags.isInvalid) {
String resName = mType.getName() + flags.getQualifiers(); String resName = mType.getName() + flags.getQualifiers();
if (mKeepBroken) { if (mKeepBroken) {
LOGGER.warning("Invalid config flags detected: " + resName); LOGGER.warning("Invalid config flags detected: " + resName);
} else { } else {
LOGGER.warning("Invalid config flags detected. Dropping resources: " LOGGER.warning("Invalid config flags detected. Dropping resources: "
+ resName); + resName);
} }
} }
mConfig = flags.isInvalid && !mKeepBroken ? null : mPkg mConfig = flags.isInvalid && !mKeepBroken ? null : mPkg
.getOrCreateConfig(flags); .getOrCreateConfig(flags);
for (int i = 0; i < entryOffsets.length; i++) { for (int i = 0; i < entryOffsets.length; i++) {
if (entryOffsets[i] != -1) { if (entryOffsets[i] != -1) {
mMissingResSpecs[i] = false; mMissingResSpecs[i] = false;
mResId = (mResId & 0xffff0000) | i; mResId = (mResId & 0xffff0000) | i;
readEntry(); readEntry();
} }
} }
return mConfig; return mConfig;
} }
private void readEntry() throws IOException, AndrolibException { private void readEntry() throws IOException, AndrolibException {
/* size */mIn.skipBytes(2); /* size */mIn.skipBytes(2);
short flags = mIn.readShort(); short flags = mIn.readShort();
int specNamesId = mIn.readInt(); int specNamesId = mIn.readInt();
ResValue value = (flags & ENTRY_FLAG_COMPLEX) == 0 ? readValue() ResValue value = (flags & ENTRY_FLAG_COMPLEX) == 0 ? readValue()
: readComplexEntry(); : readComplexEntry();
if (mConfig == null) { if (mConfig == null) {
return; return;
} }
ResID resId = new ResID(mResId); ResID resId = new ResID(mResId);
ResResSpec spec; ResResSpec spec;
if (mPkg.hasResSpec(resId)) { if (mPkg.hasResSpec(resId)) {
spec = mPkg.getResSpec(resId); spec = mPkg.getResSpec(resId);
} else { } else {
spec = new ResResSpec(resId, mSpecNames.getString(specNamesId), spec = new ResResSpec(resId, mSpecNames.getString(specNamesId),
mPkg, mType); mPkg, mType);
mPkg.addResSpec(spec); mPkg.addResSpec(spec);
mType.addResSpec(spec); mType.addResSpec(spec);
} }
ResResource res = new ResResource(mConfig, spec, value); ResResource res = new ResResource(mConfig, spec, value);
mConfig.addResource(res); mConfig.addResource(res);
spec.addResource(res); spec.addResource(res);
mPkg.addResource(res); mPkg.addResource(res);
} }
private ResBagValue readComplexEntry() throws IOException, private ResBagValue readComplexEntry() throws IOException,
AndrolibException { AndrolibException {
int parent = mIn.readInt(); int parent = mIn.readInt();
int count = mIn.readInt(); int count = mIn.readInt();
ResValueFactory factory = mPkg.getValueFactory(); ResValueFactory factory = mPkg.getValueFactory();
Duo<Integer, ResScalarValue>[] items = new Duo[count]; Duo<Integer, ResScalarValue>[] items = new Duo[count];
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
items[i] = new Duo<Integer, ResScalarValue>(mIn.readInt(), items[i] = new Duo<Integer, ResScalarValue>(mIn.readInt(),
(ResScalarValue) readValue()); (ResScalarValue) readValue());
} }
return factory.bagFactory(parent, items); return factory.bagFactory(parent, items);
} }
private ResValue readValue() throws IOException, AndrolibException { private ResValue readValue() throws IOException, AndrolibException {
/* size */mIn.skipCheckShort((short) 8); /* size */mIn.skipCheckShort((short) 8);
/* zero */mIn.skipCheckByte((byte) 0); /* zero */mIn.skipCheckByte((byte) 0);
byte type = mIn.readByte(); byte type = mIn.readByte();
int data = mIn.readInt(); int data = mIn.readInt();
return type == TypedValue.TYPE_STRING ? mPkg.getValueFactory().factory( return type == TypedValue.TYPE_STRING ? mPkg.getValueFactory().factory(
mTableStrings.getHTML(data)) : mPkg.getValueFactory().factory( mTableStrings.getHTML(data)) : mPkg.getValueFactory().factory(
type, data, null); type, data, null);
} }
private ResConfigFlags readConfigFlags() throws IOException, private ResConfigFlags readConfigFlags() throws IOException,
AndrolibException { AndrolibException {
int size = mIn.readInt(); int size = mIn.readInt();
if (size < 28) { if (size < 28) {
throw new AndrolibException("Config size < 28"); throw new AndrolibException("Config size < 28");
} }
boolean isInvalid = false; boolean isInvalid = false;
short mcc = mIn.readShort(); short mcc = mIn.readShort();
short mnc = mIn.readShort(); short mnc = mIn.readShort();
char[] language = new char[] { (char) mIn.readByte(), char[] language = new char[] { (char) mIn.readByte(),
(char) mIn.readByte() }; (char) mIn.readByte() };
char[] country = new char[] { (char) mIn.readByte(), char[] country = new char[] { (char) mIn.readByte(),
(char) mIn.readByte() }; (char) mIn.readByte() };
byte orientation = mIn.readByte(); byte orientation = mIn.readByte();
byte touchscreen = mIn.readByte(); byte touchscreen = mIn.readByte();
int density = mIn.readUnsignedShort(); int density = mIn.readUnsignedShort();
byte keyboard = mIn.readByte(); byte keyboard = mIn.readByte();
byte navigation = mIn.readByte(); byte navigation = mIn.readByte();
byte inputFlags = mIn.readByte(); byte inputFlags = mIn.readByte();
/* inputPad0 */mIn.skipBytes(1); /* inputPad0 */mIn.skipBytes(1);
short screenWidth = mIn.readShort(); short screenWidth = mIn.readShort();
short screenHeight = mIn.readShort(); short screenHeight = mIn.readShort();
short sdkVersion = mIn.readShort(); short sdkVersion = mIn.readShort();
/* minorVersion, now must always be 0 */mIn.skipBytes(2); /* minorVersion, now must always be 0 */mIn.skipBytes(2);
byte screenLayout = 0; byte screenLayout = 0;
byte uiMode = 0; byte uiMode = 0;
short smallestScreenWidthDp = 0; short smallestScreenWidthDp = 0;
if (size >= 32) { if (size >= 32) {
screenLayout = mIn.readByte(); screenLayout = mIn.readByte();
uiMode = mIn.readByte(); uiMode = mIn.readByte();
smallestScreenWidthDp = mIn.readShort(); smallestScreenWidthDp = mIn.readShort();
} }
short screenWidthDp = 0; short screenWidthDp = 0;
short screenHeightDp = 0; short screenHeightDp = 0;
if (size >= 36) { if (size >= 36) {
screenWidthDp = mIn.readShort(); screenWidthDp = mIn.readShort();
screenHeightDp = mIn.readShort(); screenHeightDp = mIn.readShort();
} }
short layoutDirection = 0; short layoutDirection = 0;
if (size >= 38) { if (size >= 38) {
layoutDirection = mIn.readShort(); layoutDirection = mIn.readShort();
} }
int exceedingSize = size - KNOWN_CONFIG_BYTES; int exceedingSize = size - KNOWN_CONFIG_BYTES;
if (exceedingSize > 0) { if (exceedingSize > 0) {
byte[] buf = new byte[exceedingSize]; byte[] buf = new byte[exceedingSize];
mIn.readFully(buf); mIn.readFully(buf);
BigInteger exceedingBI = new BigInteger(1, buf); BigInteger exceedingBI = new BigInteger(1, buf);
if (exceedingBI.equals(BigInteger.ZERO)) { if (exceedingBI.equals(BigInteger.ZERO)) {
LOGGER.fine(String LOGGER.fine(String
.format("Config flags size > %d, but exceeding bytes are all zero, so it should be ok.", .format("Config flags size > %d, but exceeding bytes are all zero, so it should be ok.",
KNOWN_CONFIG_BYTES)); KNOWN_CONFIG_BYTES));
} else { } else {
LOGGER.warning(String.format( LOGGER.warning(String.format(
"Config flags size > %d. Exceeding bytes: 0x%X.", "Config flags size > %d. Exceeding bytes: 0x%X.",
KNOWN_CONFIG_BYTES, exceedingBI)); KNOWN_CONFIG_BYTES, exceedingBI));
isInvalid = true; isInvalid = true;
} }
} }
return new ResConfigFlags(mcc, mnc, language, country, layoutDirection, return new ResConfigFlags(mcc, mnc, language, country, layoutDirection,
orientation, touchscreen, density, keyboard, navigation, orientation, touchscreen, density, keyboard, navigation,
inputFlags, screenWidth, screenHeight, sdkVersion, inputFlags, screenWidth, screenHeight, sdkVersion,
screenLayout, uiMode, smallestScreenWidthDp, screenWidthDp, screenLayout, uiMode, smallestScreenWidthDp, screenWidthDp,
screenHeightDp, isInvalid); screenHeightDp, isInvalid);
} }
private void addMissingResSpecs() throws AndrolibException { private void addMissingResSpecs() throws AndrolibException {
int resId = mResId & 0xffff0000; int resId = mResId & 0xffff0000;
for (int i = 0; i < mMissingResSpecs.length; i++) { for (int i = 0; i < mMissingResSpecs.length; i++) {
if (!mMissingResSpecs[i]) { if (!mMissingResSpecs[i]) {
continue; continue;
} }
ResResSpec spec = new ResResSpec(new ResID(resId | i), ResResSpec spec = new ResResSpec(new ResID(resId | i),
String.format("APKTOOL_DUMMY_%04x", i), mPkg, mType); String.format("APKTOOL_DUMMY_%04x", i), mPkg, mType);
mPkg.addResSpec(spec); mPkg.addResSpec(spec);
mType.addResSpec(spec); mType.addResSpec(spec);
if (mConfig == null) { if (mConfig == null) {
mConfig = mPkg.getOrCreateConfig(new ResConfigFlags()); mConfig = mPkg.getOrCreateConfig(new ResConfigFlags());
} }
ResValue value = new ResBoolValue(false, null); ResValue value = new ResBoolValue(false, null);
ResResource res = new ResResource(mConfig, spec, value); ResResource res = new ResResource(mConfig, spec, value);
mPkg.addResource(res); mPkg.addResource(res);
mConfig.addResource(res); mConfig.addResource(res);
spec.addResource(res); spec.addResource(res);
} }
} }
private Header nextChunk() throws IOException { private Header nextChunk() throws IOException {
return mHeader = Header.read(mIn); return mHeader = Header.read(mIn);
} }
private void checkChunkType(int expectedType) throws AndrolibException { private void checkChunkType(int expectedType) throws AndrolibException {
if (mHeader.type != expectedType) { if (mHeader.type != expectedType) {
throw new AndrolibException(String.format( throw new AndrolibException(String.format(
"Invalid chunk type: expected=0x%08x, got=0x%08x", "Invalid chunk type: expected=0x%08x, got=0x%08x",
expectedType, mHeader.type)); expectedType, mHeader.type));
} }
} }
private void nextChunkCheckType(int expectedType) throws IOException, private void nextChunkCheckType(int expectedType) throws IOException,
AndrolibException { AndrolibException {
nextChunk(); nextChunk();
checkChunkType(expectedType); checkChunkType(expectedType);
} }
private final ExtDataInput mIn; private final ExtDataInput mIn;
private final ResTable mResTable; private final ResTable mResTable;
private final CountingInputStream mCountIn; private final CountingInputStream mCountIn;
private final List<FlagsOffset> mFlagsOffsets; private final List<FlagsOffset> mFlagsOffsets;
private final boolean mKeepBroken; private final boolean mKeepBroken;
private Header mHeader; private Header mHeader;
private StringBlock mTableStrings; private StringBlock mTableStrings;
private StringBlock mTypeNames; private StringBlock mTypeNames;
private StringBlock mSpecNames; private StringBlock mSpecNames;
private ResPackage mPkg; private ResPackage mPkg;
private ResType mType; private ResType mType;
private ResConfig mConfig; private ResConfig mConfig;
private int mResId; private int mResId;
private boolean[] mMissingResSpecs; private boolean[] mMissingResSpecs;
private final static short ENTRY_FLAG_COMPLEX = 0x0001; private final static short ENTRY_FLAG_COMPLEX = 0x0001;
public static class Header { public static class Header {
public final short type; public final short type;
public final int chunkSize; public final int chunkSize;
public Header(short type, int size) { public Header(short type, int size) {
this.type = type; this.type = type;
this.chunkSize = size; this.chunkSize = size;
} }
public static Header read(ExtDataInput in) throws IOException { public static Header read(ExtDataInput in) throws IOException {
short type; short type;
try { try {
type = in.readShort(); type = in.readShort();
} catch (EOFException ex) { } catch (EOFException ex) {
return new Header(TYPE_NONE, 0); return new Header(TYPE_NONE, 0);
} }
in.skipBytes(2); in.skipBytes(2);
return new Header(type, in.readInt()); return new Header(type, in.readInt());
} }
public final static short TYPE_NONE = -1, TYPE_TABLE = 0x0002, public final static short TYPE_NONE = -1, TYPE_TABLE = 0x0002,
TYPE_PACKAGE = 0x0200, TYPE_TYPE = 0x0202, TYPE_PACKAGE = 0x0200, TYPE_TYPE = 0x0202,
TYPE_CONFIG = 0x0201; TYPE_CONFIG = 0x0201;
} }
public static class FlagsOffset { public static class FlagsOffset {
public final int offset; public final int offset;
public final int count; public final int count;
public FlagsOffset(int offset, int count) { public FlagsOffset(int offset, int count) {
this.offset = offset; this.offset = offset;
this.count = count; this.count = count;
} }
} }
private static final Logger LOGGER = Logger.getLogger(ARSCDecoder.class private static final Logger LOGGER = Logger.getLogger(ARSCDecoder.class
.getName()); .getName());
private static final int KNOWN_CONFIG_BYTES = 38; private static final int KNOWN_CONFIG_BYTES = 38;
public static class ARSCData { public static class ARSCData {
public ARSCData(ResPackage[] packages, FlagsOffset[] flagsOffsets, public ARSCData(ResPackage[] packages, FlagsOffset[] flagsOffsets,
ResTable resTable) { ResTable resTable) {
mPackages = packages; mPackages = packages;
mFlagsOffsets = flagsOffsets; mFlagsOffsets = flagsOffsets;
mResTable = resTable; mResTable = resTable;
} }
public FlagsOffset[] getFlagsOffsets() { public FlagsOffset[] getFlagsOffsets() {
return mFlagsOffsets; return mFlagsOffsets;
} }
public ResPackage[] getPackages() { public ResPackage[] getPackages() {
return mPackages; return mPackages;
} }
public ResPackage getOnePackage() throws AndrolibException { public ResPackage getOnePackage() throws AndrolibException {
if (mPackages.length <= 0) { if (mPackages.length <= 0) {
throw new AndrolibException( throw new AndrolibException(
"Arsc file contains zero packages"); "Arsc file contains zero packages");
@ -426,9 +426,9 @@ public class ARSCDecoder {
int id = findPackageWithMostResSpecs(); int id = findPackageWithMostResSpecs();
LOGGER.warning("Arsc file contains multiple packages. Using package " + mPackages[id].getName() + " as default."); LOGGER.warning("Arsc file contains multiple packages. Using package " + mPackages[id].getName() + " as default.");
return mPackages[id]; return mPackages[id];
} }
return mPackages[0]; return mPackages[0];
} }
public int findPackageWithMostResSpecs() { public int findPackageWithMostResSpecs() {
int count = -1; int count = -1;
@ -448,12 +448,12 @@ public class ARSCDecoder {
return id; return id;
} }
public ResTable getResTable() { public ResTable getResTable() {
return mResTable; return mResTable;
} }
private final ResPackage[] mPackages; private final ResPackage[] mPackages;
private final FlagsOffset[] mFlagsOffsets; private final FlagsOffset[] mFlagsOffsets;
private final ResTable mResTable; private final ResTable mResTable;
} }
} }

View File

@ -30,113 +30,113 @@ import org.apache.commons.io.IOUtils;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public class Res9patchStreamDecoder implements ResStreamDecoder { public class Res9patchStreamDecoder implements ResStreamDecoder {
@Override @Override
public void decode(InputStream in, OutputStream out) public void decode(InputStream in, OutputStream out)
throws AndrolibException { throws AndrolibException {
try { try {
byte[] data = IOUtils.toByteArray(in); byte[] data = IOUtils.toByteArray(in);
BufferedImage im = ImageIO.read(new ByteArrayInputStream(data)); BufferedImage im = ImageIO.read(new ByteArrayInputStream(data));
int w = im.getWidth(), h = im.getHeight(); int w = im.getWidth(), h = im.getHeight();
ImageTypeSpecifier its = ImageTypeSpecifier.createFromRenderedImage( im ); ImageTypeSpecifier its = ImageTypeSpecifier.createFromRenderedImage( im );
BufferedImage im2 = its.createBufferedImage( w+2, h+2 ); BufferedImage im2 = its.createBufferedImage( w+2, h+2 );
im2.getRaster().setRect(1, 1, im.getRaster()); im2.getRaster().setRect(1, 1, im.getRaster());
NinePatch np = getNinePatch(data); NinePatch np = getNinePatch(data);
drawHLine(im2, h + 1, np.padLeft + 1, w - np.padRight); drawHLine(im2, h + 1, np.padLeft + 1, w - np.padRight);
drawVLine(im2, w + 1, np.padTop + 1, h - np.padBottom); drawVLine(im2, w + 1, np.padTop + 1, h - np.padBottom);
int[] xDivs = np.xDivs; int[] xDivs = np.xDivs;
for (int i = 0; i < xDivs.length; i += 2) { for (int i = 0; i < xDivs.length; i += 2) {
drawHLine(im2, 0, xDivs[i] + 1, xDivs[i + 1]); drawHLine(im2, 0, xDivs[i] + 1, xDivs[i + 1]);
} }
int[] yDivs = np.yDivs; int[] yDivs = np.yDivs;
for (int i = 0; i < yDivs.length; i += 2) { for (int i = 0; i < yDivs.length; i += 2) {
drawVLine(im2, 0, yDivs[i] + 1, yDivs[i + 1]); drawVLine(im2, 0, yDivs[i] + 1, yDivs[i + 1]);
} }
ImageIO.write(im2, "png", out); ImageIO.write(im2, "png", out);
} catch (IOException ex) { } catch (IOException ex) {
throw new AndrolibException(ex); throw new AndrolibException(ex);
} catch (NullPointerException ex) { } catch (NullPointerException ex) {
// In my case this was triggered because a .png file was // In my case this was triggered because a .png file was
// containing a html document instead of an image. // containing a html document instead of an image.
// This could be more verbose and try to MIME ? // This could be more verbose and try to MIME ?
throw new AndrolibException(ex); throw new AndrolibException(ex);
} }
} }
private NinePatch getNinePatch(byte[] data) throws AndrolibException, private NinePatch getNinePatch(byte[] data) throws AndrolibException,
IOException { IOException {
ExtDataInput di = new ExtDataInput(new ByteArrayInputStream(data)); ExtDataInput di = new ExtDataInput(new ByteArrayInputStream(data));
find9patchChunk(di); find9patchChunk(di);
return NinePatch.decode(di); return NinePatch.decode(di);
} }
private void find9patchChunk(DataInput di) throws AndrolibException, private void find9patchChunk(DataInput di) throws AndrolibException,
IOException { IOException {
di.skipBytes(8); di.skipBytes(8);
while (true) { while (true) {
int size; int size;
try { try {
size = di.readInt(); size = di.readInt();
} catch (IOException ex) { } catch (IOException ex) {
throw new CantFind9PatchChunk("Cant find nine patch chunk", ex); throw new CantFind9PatchChunk("Cant find nine patch chunk", ex);
} }
if (di.readInt() == NP_CHUNK_TYPE) { if (di.readInt() == NP_CHUNK_TYPE) {
return; return;
} }
di.skipBytes(size + 4); di.skipBytes(size + 4);
} }
} }
private void drawHLine(BufferedImage im, int y, int x1, int x2) { private void drawHLine(BufferedImage im, int y, int x1, int x2) {
for (int x = x1; x <= x2; x++) { for (int x = x1; x <= x2; x++) {
im.setRGB(x, y, NP_COLOR); im.setRGB(x, y, NP_COLOR);
} }
} }
private void drawVLine(BufferedImage im, int x, int y1, int y2) { private void drawVLine(BufferedImage im, int x, int y1, int y2) {
for (int y = y1; y <= y2; y++) { for (int y = y1; y <= y2; y++) {
im.setRGB(x, y, NP_COLOR); im.setRGB(x, y, NP_COLOR);
} }
} }
private static final int NP_CHUNK_TYPE = 0x6e705463; // npTc private static final int NP_CHUNK_TYPE = 0x6e705463; // npTc
private static final int NP_COLOR = 0xff000000; private static final int NP_COLOR = 0xff000000;
private static class NinePatch { private static class NinePatch {
public final int padLeft, padRight, padTop, padBottom; public final int padLeft, padRight, padTop, padBottom;
public final int[] xDivs, yDivs; public final int[] xDivs, yDivs;
public NinePatch(int padLeft, int padRight, int padTop, int padBottom, public NinePatch(int padLeft, int padRight, int padTop, int padBottom,
int[] xDivs, int[] yDivs) { int[] xDivs, int[] yDivs) {
this.padLeft = padLeft; this.padLeft = padLeft;
this.padRight = padRight; this.padRight = padRight;
this.padTop = padTop; this.padTop = padTop;
this.padBottom = padBottom; this.padBottom = padBottom;
this.xDivs = xDivs; this.xDivs = xDivs;
this.yDivs = yDivs; this.yDivs = yDivs;
} }
public static NinePatch decode(ExtDataInput di) throws IOException { public static NinePatch decode(ExtDataInput di) throws IOException {
di.skipBytes(1); di.skipBytes(1);
byte numXDivs = di.readByte(); byte numXDivs = di.readByte();
byte numYDivs = di.readByte(); byte numYDivs = di.readByte();
di.skipBytes(1); di.skipBytes(1);
di.skipBytes(8); di.skipBytes(8);
int padLeft = di.readInt(); int padLeft = di.readInt();
int padRight = di.readInt(); int padRight = di.readInt();
int padTop = di.readInt(); int padTop = di.readInt();
int padBottom = di.readInt(); int padBottom = di.readInt();
di.skipBytes(4); di.skipBytes(4);
int[] xDivs = di.readIntArray(numXDivs); int[] xDivs = di.readIntArray(numXDivs);
int[] yDivs = di.readIntArray(numYDivs); int[] yDivs = di.readIntArray(numYDivs);
return new NinePatch(padLeft, padRight, padTop, padBottom, xDivs, return new NinePatch(padLeft, padRight, padTop, padBottom, xDivs,
yDivs); yDivs);
} }
} }
} }

View File

@ -25,31 +25,31 @@ import brut.androlib.res.data.value.ResScalarValue;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public class ResAttrDecoder { public class ResAttrDecoder {
public String decode(int type, int value, String rawValue, int attrResId) public String decode(int type, int value, String rawValue, int attrResId)
throws AndrolibException { throws AndrolibException {
ResScalarValue resValue = mCurrentPackage.getValueFactory().factory( ResScalarValue resValue = mCurrentPackage.getValueFactory().factory(
type, value, rawValue); type, value, rawValue);
String decoded = null; String decoded = null;
if (attrResId != 0) { if (attrResId != 0) {
ResAttr attr = (ResAttr) getCurrentPackage().getResTable() ResAttr attr = (ResAttr) getCurrentPackage().getResTable()
.getResSpec(attrResId).getDefaultResource().getValue(); .getResSpec(attrResId).getDefaultResource().getValue();
decoded = attr.convertToResXmlFormat(resValue); decoded = attr.convertToResXmlFormat(resValue);
} }
return decoded != null ? decoded : resValue.encodeAsResXmlAttr(); return decoded != null ? decoded : resValue.encodeAsResXmlAttr();
} }
public ResPackage getCurrentPackage() throws AndrolibException { public ResPackage getCurrentPackage() throws AndrolibException {
if (mCurrentPackage == null) { if (mCurrentPackage == null) {
throw new AndrolibException("Current package not set"); throw new AndrolibException("Current package not set");
} }
return mCurrentPackage; return mCurrentPackage;
} }
public void setCurrentPackage(ResPackage currentPackage) { public void setCurrentPackage(ResPackage currentPackage) {
mCurrentPackage = currentPackage; mCurrentPackage = currentPackage;
} }
private ResPackage mCurrentPackage; private ResPackage mCurrentPackage;
} }

View File

@ -31,121 +31,121 @@ import java.util.logging.Logger;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public class ResFileDecoder { public class ResFileDecoder {
private final ResStreamDecoderContainer mDecoders; private final ResStreamDecoderContainer mDecoders;
public ResFileDecoder(ResStreamDecoderContainer decoders) { public ResFileDecoder(ResStreamDecoderContainer decoders) {
this.mDecoders = decoders; this.mDecoders = decoders;
} }
public void decode(ResResource res, Directory inDir, Directory outDir) public void decode(ResResource res, Directory inDir, Directory outDir)
throws AndrolibException { throws AndrolibException {
ResFileValue fileValue = (ResFileValue) res.getValue(); ResFileValue fileValue = (ResFileValue) res.getValue();
String inFileName = fileValue.getStrippedPath(); String inFileName = fileValue.getStrippedPath();
String outResName = res.getFilePath(); String outResName = res.getFilePath();
String typeName = res.getResSpec().getType().getName(); String typeName = res.getResSpec().getType().getName();
String ext = null; String ext = null;
String outFileName; String outFileName;
int extPos = inFileName.lastIndexOf("."); int extPos = inFileName.lastIndexOf(".");
if (extPos == -1) { if (extPos == -1) {
outFileName = outResName; outFileName = outResName;
} else { } else {
ext = inFileName.substring(extPos); ext = inFileName.substring(extPos);
outFileName = outResName + ext; outFileName = outResName + ext;
} }
try { try {
if (typeName.equals("raw")) { if (typeName.equals("raw")) {
decode(inDir, inFileName, outDir, outFileName, "raw"); decode(inDir, inFileName, outDir, outFileName, "raw");
return; return;
} }
if (typeName.equals("drawable") || typeName.equals("mipmap")) { if (typeName.equals("drawable") || typeName.equals("mipmap")) {
if (inFileName.toLowerCase().endsWith(".9.png")) { if (inFileName.toLowerCase().endsWith(".9.png")) {
outFileName = outResName + ".9" + ext; outFileName = outResName + ".9" + ext;
// check for htc .r.9.png // check for htc .r.9.png
if (inFileName.toLowerCase().endsWith(".r.9.png")) { if (inFileName.toLowerCase().endsWith(".r.9.png")) {
outFileName = outResName + ".r.9" + ext; outFileName = outResName + ".r.9" + ext;
} }
try { try {
decode(inDir, inFileName, outDir, outFileName, "9patch"); decode(inDir, inFileName, outDir, outFileName, "9patch");
return; return;
} catch (CantFind9PatchChunk ex) { } catch (CantFind9PatchChunk ex) {
LOGGER.log( LOGGER.log(
Level.WARNING, Level.WARNING,
String.format( String.format(
"Cant find 9patch chunk in file: \"%s\". Renaming it to *.png.", "Cant find 9patch chunk in file: \"%s\". Renaming it to *.png.",
inFileName), ex); inFileName), ex);
outDir.removeFile(outFileName); outDir.removeFile(outFileName);
outFileName = outResName + ext; outFileName = outResName + ext;
} }
} }
if (!".xml".equals(ext)) { if (!".xml".equals(ext)) {
decode(inDir, inFileName, outDir, outFileName, "raw"); decode(inDir, inFileName, outDir, outFileName, "raw");
return; return;
} }
} }
decode(inDir, inFileName, outDir, outFileName, "xml"); decode(inDir, inFileName, outDir, outFileName, "xml");
} catch (AndrolibException ex) { } catch (AndrolibException ex) {
LOGGER.log(Level.SEVERE, String.format( LOGGER.log(Level.SEVERE, String.format(
"Could not decode file, replacing by FALSE value: %s", "Could not decode file, replacing by FALSE value: %s",
inFileName, outFileName), ex); inFileName, outFileName), ex);
res.replace(new ResBoolValue(false, null)); res.replace(new ResBoolValue(false, null));
} }
} }
public void decode(Directory inDir, String inFileName, Directory outDir, public void decode(Directory inDir, String inFileName, Directory outDir,
String outFileName, String decoder) throws AndrolibException { String outFileName, String decoder) throws AndrolibException {
InputStream in = null; InputStream in = null;
OutputStream out = null; OutputStream out = null;
try { try {
in = inDir.getFileInput(inFileName); in = inDir.getFileInput(inFileName);
out = outDir.getFileOutput(outFileName); out = outDir.getFileOutput(outFileName);
mDecoders.decode(in, out, decoder); mDecoders.decode(in, out, decoder);
} catch (DirectoryException ex) { } catch (DirectoryException ex) {
throw new AndrolibException(ex); throw new AndrolibException(ex);
} finally { } finally {
try { try {
if (in != null) { if (in != null) {
in.close(); in.close();
} }
if (out != null) { if (out != null) {
out.close(); out.close();
} }
} catch (IOException ex) { } catch (IOException ex) {
throw new AndrolibException(ex); throw new AndrolibException(ex);
} }
} }
} }
public void decodeManifest(Directory inDir, String inFileName, public void decodeManifest(Directory inDir, String inFileName,
Directory outDir, String outFileName) throws AndrolibException { Directory outDir, String outFileName) throws AndrolibException {
InputStream in = null; InputStream in = null;
OutputStream out = null; OutputStream out = null;
try { try {
in = inDir.getFileInput(inFileName); in = inDir.getFileInput(inFileName);
out = outDir.getFileOutput(outFileName); out = outDir.getFileOutput(outFileName);
((XmlPullStreamDecoder) mDecoders.getDecoder("xml")) ((XmlPullStreamDecoder) mDecoders.getDecoder("xml"))
.decodeManifest(in, out); .decodeManifest(in, out);
} catch (DirectoryException ex) { } catch (DirectoryException ex) {
throw new AndrolibException(ex); throw new AndrolibException(ex);
} finally { } finally {
try { try {
if (in != null) { if (in != null) {
in.close(); in.close();
} }
if (out != null) { if (out != null) {
out.close(); out.close();
} }
} catch (IOException ex) { } catch (IOException ex) {
throw new AndrolibException(ex); throw new AndrolibException(ex);
} }
} }
} }
private final static Logger LOGGER = Logger.getLogger(ResFileDecoder.class private final static Logger LOGGER = Logger.getLogger(ResFileDecoder.class
.getName()); .getName());
} }

View File

@ -26,13 +26,13 @@ import org.apache.commons.io.IOUtils;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public class ResRawStreamDecoder implements ResStreamDecoder { public class ResRawStreamDecoder implements ResStreamDecoder {
@Override @Override
public void decode(InputStream in, OutputStream out) public void decode(InputStream in, OutputStream out)
throws AndrolibException { throws AndrolibException {
try { try {
IOUtils.copy(in, out); IOUtils.copy(in, out);
} catch (IOException ex) { } catch (IOException ex) {
throw new AndrolibException("Could not decode raw stream", ex); throw new AndrolibException("Could not decode raw stream", ex);
} }
} }
} }

View File

@ -24,6 +24,6 @@ import java.io.OutputStream;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public interface ResStreamDecoder { public interface ResStreamDecoder {
public void decode(InputStream in, OutputStream out) public void decode(InputStream in, OutputStream out)
throws AndrolibException; throws AndrolibException;
} }

View File

@ -26,22 +26,22 @@ import java.util.Map;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public class ResStreamDecoderContainer { public class ResStreamDecoderContainer {
private final Map<String, ResStreamDecoder> mDecoders = new HashMap<String, ResStreamDecoder>(); private final Map<String, ResStreamDecoder> mDecoders = new HashMap<String, ResStreamDecoder>();
public void decode(InputStream in, OutputStream out, String decoderName) public void decode(InputStream in, OutputStream out, String decoderName)
throws AndrolibException { throws AndrolibException {
getDecoder(decoderName).decode(in, out); getDecoder(decoderName).decode(in, out);
} }
public ResStreamDecoder getDecoder(String name) throws AndrolibException { public ResStreamDecoder getDecoder(String name) throws AndrolibException {
ResStreamDecoder decoder = mDecoders.get(name); ResStreamDecoder decoder = mDecoders.get(name);
if (decoder == null) { if (decoder == null) {
throw new AndrolibException("Undefined decoder: " + name); throw new AndrolibException("Undefined decoder: " + name);
} }
return decoder; return decoder;
} }
public void setDecoder(String name, ResStreamDecoder decoder) { public void setDecoder(String name, ResStreamDecoder decoder) {
mDecoders.put(name, decoder); mDecoders.put(name, decoder);
} }
} }

View File

@ -28,275 +28,275 @@ import java.util.logging.Logger;
/** /**
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
* @author Dmitry Skiba * @author Dmitry Skiba
* *
* Block of strings, used in binary xml and arsc. * Block of strings, used in binary xml and arsc.
* *
* TODO: - implement get() * TODO: - implement get()
* *
*/ */
public class StringBlock { public class StringBlock {
/** /**
* Reads whole (including chunk type) string block from stream. Stream must * Reads whole (including chunk type) string block from stream. Stream must
* be at the chunk type. * be at the chunk type.
*/ */
public static StringBlock read(ExtDataInput reader) throws IOException { public static StringBlock read(ExtDataInput reader) throws IOException {
reader.skipCheckInt(CHUNK_TYPE); reader.skipCheckInt(CHUNK_TYPE);
int chunkSize = reader.readInt(); int chunkSize = reader.readInt();
int stringCount = reader.readInt(); int stringCount = reader.readInt();
int styleOffsetCount = reader.readInt(); int styleOffsetCount = reader.readInt();
int flags = reader.readInt(); int flags = reader.readInt();
int stringsOffset = reader.readInt(); int stringsOffset = reader.readInt();
int stylesOffset = reader.readInt(); int stylesOffset = reader.readInt();
StringBlock block = new StringBlock(); StringBlock block = new StringBlock();
block.m_isUTF8 = (flags & UTF8_FLAG) != 0; block.m_isUTF8 = (flags & UTF8_FLAG) != 0;
block.m_stringOffsets = reader.readIntArray(stringCount); block.m_stringOffsets = reader.readIntArray(stringCount);
block.m_stringOwns = new int[stringCount]; block.m_stringOwns = new int[stringCount];
Arrays.fill(block.m_stringOwns, -1); Arrays.fill(block.m_stringOwns, -1);
if (styleOffsetCount != 0) { if (styleOffsetCount != 0) {
block.m_styleOffsets = reader.readIntArray(styleOffsetCount); block.m_styleOffsets = reader.readIntArray(styleOffsetCount);
} }
{ {
int size = ((stylesOffset == 0) ? chunkSize : stylesOffset) int size = ((stylesOffset == 0) ? chunkSize : stylesOffset)
- stringsOffset; - stringsOffset;
if ((size % 4) != 0) { if ((size % 4) != 0) {
throw new IOException("String data size is not multiple of 4 (" throw new IOException("String data size is not multiple of 4 ("
+ size + ")."); + size + ").");
} }
block.m_strings = new byte[size]; block.m_strings = new byte[size];
reader.readFully(block.m_strings); reader.readFully(block.m_strings);
} }
if (stylesOffset != 0) { if (stylesOffset != 0) {
int size = (chunkSize - stylesOffset); int size = (chunkSize - stylesOffset);
if ((size % 4) != 0) { if ((size % 4) != 0) {
throw new IOException("Style data size is not multiple of 4 (" throw new IOException("Style data size is not multiple of 4 ("
+ size + ")."); + size + ").");
} }
block.m_styles = reader.readIntArray(size / 4); block.m_styles = reader.readIntArray(size / 4);
} }
return block; return block;
} }
/** /**
* Returns number of strings in block. * Returns number of strings in block.
*/ */
public int getCount() { public int getCount() {
return m_stringOffsets != null ? m_stringOffsets.length : 0; return m_stringOffsets != null ? m_stringOffsets.length : 0;
} }
/** /**
* Returns raw string (without any styling information) at specified index. * Returns raw string (without any styling information) at specified index.
*/ */
public String getString(int index) { public String getString(int index) {
if (index < 0 || m_stringOffsets == null || index >= m_stringOffsets.length) { if (index < 0 || m_stringOffsets == null || index >= m_stringOffsets.length) {
return null; return null;
} }
int offset = m_stringOffsets[index]; int offset = m_stringOffsets[index];
int length; int length;
if (m_isUTF8) { if (m_isUTF8) {
int[] val = getUtf8(m_strings, offset); int[] val = getUtf8(m_strings, offset);
offset = val[0]; offset = val[0];
length = val[1]; length = val[1];
} else { } else {
int[] val = getUtf16(m_strings, offset); int[] val = getUtf16(m_strings, offset);
offset += val[0]; offset += val[0];
length = val[1]; length = val[1];
} }
return decodeString(offset, length); return decodeString(offset, length);
} }
/** /**
* Not yet implemented. * Not yet implemented.
* *
* Returns string with style information (if any). * Returns string with style information (if any).
*/ */
public CharSequence get(int index) { public CharSequence get(int index) {
return getString(index); return getString(index);
} }
/** /**
* Returns string with style tags (html-like). * Returns string with style tags (html-like).
*/ */
public String getHTML(int index) { public String getHTML(int index) {
String raw = getString(index); String raw = getString(index);
if (raw == null) { if (raw == null) {
return raw; return raw;
} }
int[] style = getStyle(index); int[] style = getStyle(index);
if (style == null) { if (style == null) {
return ResXmlEncoders.escapeXmlChars(raw); return ResXmlEncoders.escapeXmlChars(raw);
} }
StringBuilder html = new StringBuilder(raw.length() + 32); StringBuilder html = new StringBuilder(raw.length() + 32);
int[] opened = new int[style.length / 3]; int[] opened = new int[style.length / 3];
int offset = 0, depth = 0; int offset = 0, depth = 0;
while (true) { while (true) {
int i = -1, j; int i = -1, j;
for (j = 0; j != style.length; j += 3) { for (j = 0; j != style.length; j += 3) {
if (style[j + 1] == -1) { if (style[j + 1] == -1) {
continue; continue;
} }
if (i == -1 || style[i + 1] > style[j + 1]) { if (i == -1 || style[i + 1] > style[j + 1]) {
i = j; i = j;
} }
} }
int start = ((i != -1) ? style[i + 1] : raw.length()); int start = ((i != -1) ? style[i + 1] : raw.length());
for (j = depth - 1; j >= 0; j--) { for (j = depth - 1; j >= 0; j--) {
int last = opened[j]; int last = opened[j];
int end = style[last + 2]; int end = style[last + 2];
if (end >= start) { if (end >= start) {
break; break;
} }
if (offset <= end) { if (offset <= end) {
html.append(ResXmlEncoders.escapeXmlChars(raw.substring( html.append(ResXmlEncoders.escapeXmlChars(raw.substring(
offset, end + 1))); offset, end + 1)));
offset = end + 1; offset = end + 1;
} }
outputStyleTag(getString(style[last]), html, true); outputStyleTag(getString(style[last]), html, true);
} }
depth = j + 1; depth = j + 1;
if (offset < start) { if (offset < start) {
html.append(ResXmlEncoders.escapeXmlChars(raw.substring(offset, html.append(ResXmlEncoders.escapeXmlChars(raw.substring(offset,
start))); start)));
offset = start; offset = start;
} }
if (i == -1) { if (i == -1) {
break; break;
} }
outputStyleTag(getString(style[i]), html, false); outputStyleTag(getString(style[i]), html, false);
style[i + 1] = -1; style[i + 1] = -1;
opened[depth++] = i; opened[depth++] = i;
} }
return html.toString(); return html.toString();
} }
private void outputStyleTag(String tag, StringBuilder builder, boolean close) { private void outputStyleTag(String tag, StringBuilder builder, boolean close) {
builder.append('<'); builder.append('<');
if (close) { if (close) {
builder.append('/'); builder.append('/');
} }
int pos = tag.indexOf(';'); int pos = tag.indexOf(';');
if (pos == -1) { if (pos == -1) {
builder.append(tag); builder.append(tag);
} else { } else {
builder.append(tag.substring(0, pos)); builder.append(tag.substring(0, pos));
if (!close) { if (!close) {
boolean loop = true; boolean loop = true;
while (loop) { while (loop) {
int pos2 = tag.indexOf('=', pos + 1); int pos2 = tag.indexOf('=', pos + 1);
builder.append(' ').append(tag.substring(pos + 1, pos2)) builder.append(' ').append(tag.substring(pos + 1, pos2))
.append("=\""); .append("=\"");
pos = tag.indexOf(';', pos2 + 1); pos = tag.indexOf(';', pos2 + 1);
String val; String val;
if (pos != -1) { if (pos != -1) {
val = tag.substring(pos2 + 1, pos); val = tag.substring(pos2 + 1, pos);
} else { } else {
loop = false; loop = false;
val = tag.substring(pos2 + 1); val = tag.substring(pos2 + 1);
} }
builder.append(ResXmlEncoders.escapeXmlChars(val)).append( builder.append(ResXmlEncoders.escapeXmlChars(val)).append(
'"'); '"');
} }
} }
} }
builder.append('>'); builder.append('>');
} }
/** /**
* Finds index of the string. Returns -1 if the string was not found. * Finds index of the string. Returns -1 if the string was not found.
*/ */
public int find(String string) { public int find(String string) {
if (string == null) { if (string == null) {
return -1; return -1;
} }
for (int i = 0; i != m_stringOffsets.length; ++i) { for (int i = 0; i != m_stringOffsets.length; ++i) {
int offset = m_stringOffsets[i]; int offset = m_stringOffsets[i];
int length = getShort(m_strings, offset); int length = getShort(m_strings, offset);
if (length != string.length()) { if (length != string.length()) {
continue; continue;
} }
int j = 0; int j = 0;
for (; j != length; ++j) { for (; j != length; ++j) {
offset += 2; offset += 2;
if (string.charAt(j) != getShort(m_strings, offset)) { if (string.charAt(j) != getShort(m_strings, offset)) {
break; break;
} }
} }
if (j == length) { if (j == length) {
return i; return i;
} }
} }
return -1; return -1;
} }
// /////////////////////////////////////////// implementation // /////////////////////////////////////////// implementation
private StringBlock() { private StringBlock() {
} }
/** /**
* Returns style information - array of int triplets, where in each triplet: * Returns style information - array of int triplets, where in each triplet:
* * first int is index of tag name ('b','i', etc.) * second int is tag * * first int is index of tag name ('b','i', etc.) * second int is tag
* start index in string * third int is tag end index in string * start index in string * third int is tag end index in string
*/ */
private int[] getStyle(int index) { private int[] getStyle(int index) {
if (m_styleOffsets == null || m_styles == null if (m_styleOffsets == null || m_styles == null
|| index >= m_styleOffsets.length) { || index >= m_styleOffsets.length) {
return null; return null;
} }
int offset = m_styleOffsets[index] / 4; int offset = m_styleOffsets[index] / 4;
int style[]; int style[];
{ {
int count = 0; int count = 0;
for (int i = offset; i < m_styles.length; ++i) { for (int i = offset; i < m_styles.length; ++i) {
if (m_styles[i] == -1) { if (m_styles[i] == -1) {
break; break;
} }
count += 1; count += 1;
} }
if (count == 0 || (count % 3) != 0) { if (count == 0 || (count % 3) != 0) {
return null; return null;
} }
style = new int[count]; style = new int[count];
} }
for (int i = offset, j = 0; i < m_styles.length;) { for (int i = offset, j = 0; i < m_styles.length;) {
if (m_styles[i] == -1) { if (m_styles[i] == -1) {
break; break;
} }
style[j++] = m_styles[i++]; style[j++] = m_styles[i++];
} }
return style; return style;
} }
private String decodeString(int offset, int length) { private String decodeString(int offset, int length) {
try { try {
return (m_isUTF8 ? UTF8_DECODER : UTF16LE_DECODER).decode( return (m_isUTF8 ? UTF8_DECODER : UTF16LE_DECODER).decode(
ByteBuffer.wrap(m_strings, offset, length)).toString(); ByteBuffer.wrap(m_strings, offset, length)).toString();
} catch (CharacterCodingException ex) { } catch (CharacterCodingException ex) {
LOGGER.log(Level.WARNING, null, ex); LOGGER.log(Level.WARNING, null, ex);
return null; return null;
} }
} }
private static final int getShort(byte[] array, int offset) { private static final int getShort(byte[] array, int offset) {
return (array[offset + 1] & 0xff) << 8 | array[offset] & 0xff; return (array[offset + 1] & 0xff) << 8 | array[offset] & 0xff;
} }
private static final int getShort(int[] array, int offset) { private static final int getShort(int[] array, int offset) {
int value = array[offset / 4]; int value = array[offset / 4];
if ((offset % 4) / 2 == 0) { if ((offset % 4) / 2 == 0) {
return (value & 0xFFFF); return (value & 0xFFFF);
} else { } else {
return (value >>> 16); return (value >>> 16);
} }
} }
private static final int[] getUtf8(byte[] array, int offset) { private static final int[] getUtf8(byte[] array, int offset) {
int val = array[offset]; int val = array[offset];
int length; int length;
@ -315,8 +315,8 @@ public class StringBlock {
while (array[offset + length] != 0) { while (array[offset + length] != 0) {
length++; length++;
} }
return new int[] { offset, length}; return new int[] { offset, length};
} }
private static final int[] getUtf16(byte[] array, int offset) { private static final int[] getUtf16(byte[] array, int offset) {
int val = ((array[offset + 1] & 0xFF) << 8 | array[offset] & 0xFF); int val = ((array[offset + 1] & 0xFF) << 8 | array[offset] & 0xFF);
@ -329,20 +329,20 @@ public class StringBlock {
return new int[] {2, val * 2}; return new int[] {2, val * 2};
} }
private int[] m_stringOffsets; private int[] m_stringOffsets;
private byte[] m_strings; private byte[] m_strings;
private int[] m_styleOffsets; private int[] m_styleOffsets;
private int[] m_styles; private int[] m_styles;
private boolean m_isUTF8; private boolean m_isUTF8;
private int[] m_stringOwns; private int[] m_stringOwns;
private final CharsetDecoder UTF16LE_DECODER = Charset.forName( private final CharsetDecoder UTF16LE_DECODER = Charset.forName(
"UTF-16LE").newDecoder(); "UTF-16LE").newDecoder();
private final CharsetDecoder UTF8_DECODER = Charset.forName("UTF-8") private final CharsetDecoder UTF8_DECODER = Charset.forName("UTF-8")
.newDecoder(); .newDecoder();
private static final Logger LOGGER = Logger.getLogger(StringBlock.class private static final Logger LOGGER = Logger.getLogger(StringBlock.class
.getName()); .getName());
// ResChunk_header = header.type (0x0001) + header.headerSize (0x001C) // ResChunk_header = header.type (0x0001) + header.headerSize (0x001C)
private static final int CHUNK_TYPE = 0x001C0001; private static final int CHUNK_TYPE = 0x001C0001;
private static final int UTF8_FLAG = 0x00000100; private static final int UTF8_FLAG = 0x00000100;
} }

View File

@ -36,56 +36,56 @@ import brut.androlib.res.util.ExtXmlSerializer;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public class XmlPullStreamDecoder implements ResStreamDecoder { public class XmlPullStreamDecoder implements ResStreamDecoder {
public XmlPullStreamDecoder(XmlPullParser parser, public XmlPullStreamDecoder(XmlPullParser parser,
ExtXmlSerializer serializer) { ExtXmlSerializer serializer) {
this.mParser = parser; this.mParser = parser;
this.mSerial = serializer; this.mSerial = serializer;
} }
@Override @Override
public void decode(InputStream in, OutputStream out) public void decode(InputStream in, OutputStream out)
throws AndrolibException { throws AndrolibException {
try { try {
XmlPullWrapperFactory factory = XmlPullWrapperFactory.newInstance(); XmlPullWrapperFactory factory = XmlPullWrapperFactory.newInstance();
XmlPullParserWrapper par = factory.newPullParserWrapper(mParser); XmlPullParserWrapper par = factory.newPullParserWrapper(mParser);
final ResTable resTable = ((AXmlResourceParser) mParser) final ResTable resTable = ((AXmlResourceParser) mParser)
.getAttrDecoder().getCurrentPackage().getResTable(); .getAttrDecoder().getCurrentPackage().getResTable();
XmlSerializerWrapper ser = new StaticXmlSerializerWrapper(mSerial, XmlSerializerWrapper ser = new StaticXmlSerializerWrapper(mSerial,
factory) { factory) {
boolean hideSdkInfo = false; boolean hideSdkInfo = false;
boolean hidePackageInfo = false; boolean hidePackageInfo = false;
@Override @Override
public void event(XmlPullParser pp) public void event(XmlPullParser pp)
throws XmlPullParserException, IOException { throws XmlPullParserException, IOException {
int type = pp.getEventType(); int type = pp.getEventType();
if (type == XmlPullParser.START_TAG) { if (type == XmlPullParser.START_TAG) {
if ("manifest".equalsIgnoreCase(pp.getName())) { if ("manifest".equalsIgnoreCase(pp.getName())) {
try { try {
hidePackageInfo = parseManifest(pp); hidePackageInfo = parseManifest(pp);
} catch (AndrolibException e) { } catch (AndrolibException e) {
} }
} else if ("uses-sdk".equalsIgnoreCase(pp.getName())) { } else if ("uses-sdk".equalsIgnoreCase(pp.getName())) {
try { try {
hideSdkInfo = parseAttr(pp); hideSdkInfo = parseAttr(pp);
if (hideSdkInfo) { if (hideSdkInfo) {
return; return;
} }
} catch (AndrolibException e) { } catch (AndrolibException e) {
} }
} }
} else if (hideSdkInfo && type == XmlPullParser.END_TAG } else if (hideSdkInfo && type == XmlPullParser.END_TAG
&& "uses-sdk".equalsIgnoreCase(pp.getName())) { && "uses-sdk".equalsIgnoreCase(pp.getName())) {
return; return;
} else if (hidePackageInfo && type == XmlPullParser.END_TAG } else if (hidePackageInfo && type == XmlPullParser.END_TAG
&& "manifest".equalsIgnoreCase(pp.getName())) { && "manifest".equalsIgnoreCase(pp.getName())) {
super.event(pp); super.event(pp);
return; return;
} }
super.event(pp); super.event(pp);
} }
private boolean parseManifest(XmlPullParser pp) private boolean parseManifest(XmlPullParser pp)
throws AndrolibException { throws AndrolibException {
@ -103,71 +103,71 @@ public class XmlPullStreamDecoder implements ResStreamDecoder {
return true; return true;
} }
private boolean parseAttr(XmlPullParser pp) private boolean parseAttr(XmlPullParser pp)
throws AndrolibException { throws AndrolibException {
ResTable restable = resTable; ResTable restable = resTable;
for (int i = 0; i < pp.getAttributeCount(); i++) { for (int i = 0; i < pp.getAttributeCount(); i++) {
final String a_ns = "http://schemas.android.com/apk/res/android"; final String a_ns = "http://schemas.android.com/apk/res/android";
String ns = pp.getAttributeNamespace(i); String ns = pp.getAttributeNamespace(i);
if (a_ns.equalsIgnoreCase(ns)) { if (a_ns.equalsIgnoreCase(ns)) {
String name = pp.getAttributeName(i); String name = pp.getAttributeName(i);
String value = pp.getAttributeValue(i); String value = pp.getAttributeValue(i);
if (name != null && value != null) { if (name != null && value != null) {
if (name.equalsIgnoreCase("minSdkVersion") if (name.equalsIgnoreCase("minSdkVersion")
|| name.equalsIgnoreCase("targetSdkVersion") || name.equalsIgnoreCase("targetSdkVersion")
|| name.equalsIgnoreCase("maxSdkVersion")) { || name.equalsIgnoreCase("maxSdkVersion")) {
restable.addSdkInfo(name, value); restable.addSdkInfo(name, value);
} else { } else {
restable.clearSdkInfo(); restable.clearSdkInfo();
return false;// Found unknown flags return false;// Found unknown flags
} }
} }
} else { } else {
resTable.clearSdkInfo(); resTable.clearSdkInfo();
if (i >= pp.getAttributeCount()) { if (i >= pp.getAttributeCount()) {
return false;// Found unknown flags return false;// Found unknown flags
} }
} }
} }
if (resTable.getAnalysisMode() == true) { if (resTable.getAnalysisMode() == true) {
return false; return false;
} else { } else {
return true; return true;
} }
} }
}; };
par.setInput(in, null); par.setInput(in, null);
ser.setOutput(out, null); ser.setOutput(out, null);
while (par.nextToken() != XmlPullParser.END_DOCUMENT) { while (par.nextToken() != XmlPullParser.END_DOCUMENT) {
ser.event(par); ser.event(par);
} }
ser.flush(); ser.flush();
} catch (XmlPullParserException ex) { } catch (XmlPullParserException ex) {
throw new AndrolibException("Could not decode XML", ex); throw new AndrolibException("Could not decode XML", ex);
} catch (IOException ex) { } catch (IOException ex) {
throw new AndrolibException("Could not decode XML", ex); throw new AndrolibException("Could not decode XML", ex);
} }
} }
public void decodeManifest(InputStream in, OutputStream out) public void decodeManifest(InputStream in, OutputStream out)
throws AndrolibException { throws AndrolibException {
mOptimizeForManifest = true; mOptimizeForManifest = true;
try { try {
decode(in, out); decode(in, out);
} finally { } finally {
mOptimizeForManifest = false; mOptimizeForManifest = false;
} }
} }
private final XmlPullParser mParser; private final XmlPullParser mParser;
private final ExtXmlSerializer mSerial; private final ExtXmlSerializer mSerial;
private boolean mOptimizeForManifest = false; private boolean mOptimizeForManifest = false;
private final static Logger LOGGER = Logger private final static Logger LOGGER = Logger
.getLogger(XmlPullStreamDecoder.class.getName()); .getLogger(XmlPullStreamDecoder.class.getName());
} }

View File

@ -27,36 +27,36 @@ import java.net.URI;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public class ExtFile extends File { public class ExtFile extends File {
public ExtFile(File file) { public ExtFile(File file) {
super(file.getPath()); super(file.getPath());
} }
public ExtFile(URI uri) { public ExtFile(URI uri) {
super(uri); super(uri);
} }
public ExtFile(File parent, String child) { public ExtFile(File parent, String child) {
super(parent, child); super(parent, child);
} }
public ExtFile(String parent, String child) { public ExtFile(String parent, String child) {
super(parent, child); super(parent, child);
} }
public ExtFile(String pathname) { public ExtFile(String pathname) {
super(pathname); super(pathname);
} }
public Directory getDirectory() throws DirectoryException { public Directory getDirectory() throws DirectoryException {
if (mDirectory == null) { if (mDirectory == null) {
if (isDirectory()) { if (isDirectory()) {
mDirectory = new FileDirectory(this); mDirectory = new FileDirectory(this);
} else { } else {
mDirectory = new ZipRODirectory(this); mDirectory = new ZipRODirectory(this);
} }
} }
return mDirectory; return mDirectory;
} }
private Directory mDirectory; private Directory mDirectory;
} }

View File

@ -23,59 +23,59 @@ import org.xmlpull.mxp1_serializer.MXSerializer;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public class ExtMXSerializer extends MXSerializer implements ExtXmlSerializer { public class ExtMXSerializer extends MXSerializer implements ExtXmlSerializer {
@Override @Override
public void startDocument(String encoding, Boolean standalone) public void startDocument(String encoding, Boolean standalone)
throws IOException, IllegalArgumentException, IllegalStateException { throws IOException, IllegalArgumentException, IllegalStateException {
super.startDocument(encoding != null ? encoding : mDefaultEncoding, super.startDocument(encoding != null ? encoding : mDefaultEncoding,
standalone); standalone);
this.newLine(); this.newLine();
} }
@Override @Override
protected void writeAttributeValue(String value, Writer out) protected void writeAttributeValue(String value, Writer out)
throws IOException { throws IOException {
if (mIsDisabledAttrEscape) { if (mIsDisabledAttrEscape) {
out.write(value); out.write(value);
return; return;
} }
super.writeAttributeValue(value, out); super.writeAttributeValue(value, out);
} }
@Override @Override
public void setOutput(OutputStream os, String encoding) throws IOException { public void setOutput(OutputStream os, String encoding) throws IOException {
super.setOutput(os, encoding != null ? encoding : mDefaultEncoding); super.setOutput(os, encoding != null ? encoding : mDefaultEncoding);
} }
@Override @Override
public Object getProperty(String name) throws IllegalArgumentException { public Object getProperty(String name) throws IllegalArgumentException {
if (PROPERTY_DEFAULT_ENCODING.equals(name)) { if (PROPERTY_DEFAULT_ENCODING.equals(name)) {
return mDefaultEncoding; return mDefaultEncoding;
} }
return super.getProperty(name); return super.getProperty(name);
} }
@Override @Override
public void setProperty(String name, Object value) public void setProperty(String name, Object value)
throws IllegalArgumentException, IllegalStateException { throws IllegalArgumentException, IllegalStateException {
if (PROPERTY_DEFAULT_ENCODING.equals(name)) { if (PROPERTY_DEFAULT_ENCODING.equals(name)) {
mDefaultEncoding = (String) value; mDefaultEncoding = (String) value;
} else { } else {
super.setProperty(name, value); super.setProperty(name, value);
} }
} }
@Override @Override
public ExtXmlSerializer newLine() throws IOException { public ExtXmlSerializer newLine() throws IOException {
super.out.write(lineSeparator); super.out.write(lineSeparator);
return this; return this;
} }
@Override @Override
public void setDisabledAttrEscape(boolean disabled) { public void setDisabledAttrEscape(boolean disabled) {
mIsDisabledAttrEscape = disabled; mIsDisabledAttrEscape = disabled;
} }
private String mDefaultEncoding; private String mDefaultEncoding;
private boolean mIsDisabledAttrEscape = false; private boolean mIsDisabledAttrEscape = false;
} }

View File

@ -24,11 +24,11 @@ import org.xmlpull.v1.XmlSerializer;
*/ */
public interface ExtXmlSerializer extends XmlSerializer { public interface ExtXmlSerializer extends XmlSerializer {
public ExtXmlSerializer newLine() throws IOException; public ExtXmlSerializer newLine() throws IOException;
public void setDisabledAttrEscape(boolean disabled); public void setDisabledAttrEscape(boolean disabled);
public static final String PROPERTY_SERIALIZER_INDENTATION = "http://xmlpull.org/v1/doc/properties.html#serializer-indentation"; public static final String PROPERTY_SERIALIZER_INDENTATION = "http://xmlpull.org/v1/doc/properties.html#serializer-indentation";
public static final String PROPERTY_SERIALIZER_LINE_SEPARATOR = "http://xmlpull.org/v1/doc/properties.html#serializer-line-separator"; public static final String PROPERTY_SERIALIZER_LINE_SEPARATOR = "http://xmlpull.org/v1/doc/properties.html#serializer-line-separator";
public static final String PROPERTY_DEFAULT_ENCODING = "DEFAULT_ENCODING"; public static final String PROPERTY_DEFAULT_ENCODING = "DEFAULT_ENCODING";
} }

View File

@ -25,6 +25,6 @@ import org.xmlpull.v1.XmlSerializer;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public interface ResValuesXmlSerializable { public interface ResValuesXmlSerializable {
public void serializeToResValuesXml(XmlSerializer serializer, public void serializeToResValuesXml(XmlSerializer serializer,
ResResource res) throws IOException, AndrolibException; ResResource res) throws IOException, AndrolibException;
} }

View File

@ -22,7 +22,7 @@ import brut.androlib.AndrolibException;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public interface ResXmlEncodable { public interface ResXmlEncodable {
public String encodeAsResXmlAttr() throws AndrolibException; public String encodeAsResXmlAttr() throws AndrolibException;
public String encodeAsResXmlValue() throws AndrolibException; public String encodeAsResXmlValue() throws AndrolibException;
} }

View File

@ -27,187 +27,187 @@ import java.util.List;
*/ */
public final class ResXmlEncoders { public final class ResXmlEncoders {
public static String escapeXmlChars(String str) { public static String escapeXmlChars(String str) {
return str.replace("&", "&amp;").replace("<", "&lt;"); return str.replace("&", "&amp;").replace("<", "&lt;");
} }
public static String encodeAsResXmlAttr(String str) { public static String encodeAsResXmlAttr(String str) {
if (str.isEmpty()) { if (str.isEmpty()) {
return str; return str;
} }
char[] chars = str.toCharArray(); char[] chars = str.toCharArray();
StringBuilder out = new StringBuilder(str.length() + 10); StringBuilder out = new StringBuilder(str.length() + 10);
switch (chars[0]) { switch (chars[0]) {
case '#': case '#':
case '@': case '@':
case '?': case '?':
out.append('\\'); out.append('\\');
} }
for (char c : chars) { for (char c : chars) {
switch (c) { switch (c) {
case '\\': case '\\':
out.append('\\'); out.append('\\');
break; break;
case '"': case '"':
out.append("&quot;"); out.append("&quot;");
continue; continue;
case '\n': case '\n':
out.append("\\n"); out.append("\\n");
continue; continue;
default: default:
if (!isPrintableChar(c)) { if (!isPrintableChar(c)) {
out.append(String.format("\\u%04x", (int) c)); out.append(String.format("\\u%04x", (int) c));
continue; continue;
} }
} }
out.append(c); out.append(c);
} }
return out.toString(); return out.toString();
} }
public static String encodeAsXmlValue(String str) { public static String encodeAsXmlValue(String str) {
if (str.isEmpty()) { if (str.isEmpty()) {
return str; return str;
} }
char[] chars = str.toCharArray(); char[] chars = str.toCharArray();
StringBuilder out = new StringBuilder(str.length() + 10); StringBuilder out = new StringBuilder(str.length() + 10);
switch (chars[0]) { switch (chars[0]) {
case '#': case '#':
case '@': case '@':
case '?': case '?':
out.append('\\'); out.append('\\');
} }
boolean isInStyleTag = false; boolean isInStyleTag = false;
int startPos = 0; int startPos = 0;
boolean enclose = false; boolean enclose = false;
boolean wasSpace = true; boolean wasSpace = true;
for (char c : chars) { for (char c : chars) {
if (isInStyleTag) { if (isInStyleTag) {
if (c == '>') { if (c == '>') {
isInStyleTag = false; isInStyleTag = false;
startPos = out.length() + 1; startPos = out.length() + 1;
enclose = false; enclose = false;
} }
} else if (c == ' ') { } else if (c == ' ') {
if (wasSpace) { if (wasSpace) {
enclose = true; enclose = true;
} }
wasSpace = true; wasSpace = true;
} else { } else {
wasSpace = false; wasSpace = false;
switch (c) { switch (c) {
case '\\': case '\\':
out.append('\\'); out.append('\\');
break; break;
case '\'': case '\'':
case '\n': case '\n':
enclose = true; enclose = true;
break; break;
case '"': case '"':
out.append('\\'); out.append('\\');
break; break;
case '<': case '<':
isInStyleTag = true; isInStyleTag = true;
if (enclose) { if (enclose) {
out.insert(startPos, '"').append('"'); out.insert(startPos, '"').append('"');
} }
break; break;
default: default:
if (!isPrintableChar(c)) { if (!isPrintableChar(c)) {
// lets not write trailing \u0000 if we are at end of string // lets not write trailing \u0000 if we are at end of string
if ((out.length() + 1) == str.length() && c == '\u0000') { if ((out.length() + 1) == str.length() && c == '\u0000') {
continue;
}
out.append(String.format("\\u%04x", (int) c));
continue; continue;
} }
out.append(String.format("\\u%04x", (int) c)); }
continue; }
} out.append(c);
} }
}
out.append(c);
}
if (enclose || wasSpace) { if (enclose || wasSpace) {
out.insert(startPos, '"').append('"'); out.insert(startPos, '"').append('"');
} }
return out.toString(); return out.toString();
} }
public static boolean hasMultipleNonPositionalSubstitutions(String str) { public static boolean hasMultipleNonPositionalSubstitutions(String str) {
Duo<List<Integer>, List<Integer>> tuple = findSubstitutions(str, 2); Duo<List<Integer>, List<Integer>> tuple = findSubstitutions(str, 2);
return ! tuple.m1.isEmpty() && tuple.m1.size() + tuple.m2.size() > 1; return ! tuple.m1.isEmpty() && tuple.m1.size() + tuple.m2.size() > 1;
} }
public static String enumerateNonPositionalSubstitutionsIfRequired(String str) { public static String enumerateNonPositionalSubstitutionsIfRequired(String str) {
Duo<List<Integer>, List<Integer>> tuple = findSubstitutions(str, 2); Duo<List<Integer>, List<Integer>> tuple = findSubstitutions(str, 2);
if (tuple.m1.isEmpty() || tuple.m1.size() + tuple.m2.size() < 2) { if (tuple.m1.isEmpty() || tuple.m1.size() + tuple.m2.size() < 2) {
return str; return str;
} }
List<Integer> subs = tuple.m1; List<Integer> subs = tuple.m1;
StringBuilder out = new StringBuilder(); StringBuilder out = new StringBuilder();
int pos = 0; int pos = 0;
int count = 0; int count = 0;
for (Integer sub : subs) { for (Integer sub : subs) {
out.append(str.substring(pos, ++sub)).append(++count).append('$'); out.append(str.substring(pos, ++sub)).append(++count).append('$');
pos = sub; pos = sub;
} }
out.append(str.substring(pos)); out.append(str.substring(pos));
return out.toString(); return out.toString();
} }
/** /**
* It returns a tuple of: * It returns a tuple of:
* - a list of offsets of non positional substitutions. non-pos is defined as any "%" which isn't "%%" nor "%\d+\$" * - a list of offsets of non positional substitutions. non-pos is defined as any "%" which isn't "%%" nor "%\d+\$"
* - a list of offsets of positional substitutions * - a list of offsets of positional substitutions
*/ */
private static Duo<List<Integer>, List<Integer>> findSubstitutions(String str, int nonPosMax) { private static Duo<List<Integer>, List<Integer>> findSubstitutions(String str, int nonPosMax) {
if (nonPosMax == -1) { if (nonPosMax == -1) {
nonPosMax = Integer.MAX_VALUE; nonPosMax = Integer.MAX_VALUE;
} }
int pos; int pos;
int pos2 = 0; int pos2 = 0;
int length = str.length(); int length = str.length();
List<Integer> nonPositional = new ArrayList<>(); List<Integer> nonPositional = new ArrayList<>();
List<Integer> positional = new ArrayList<>(); List<Integer> positional = new ArrayList<>();
while ((pos = str.indexOf('%', pos2)) != -1) { while ((pos = str.indexOf('%', pos2)) != -1) {
pos2 = pos + 1; pos2 = pos + 1;
if (pos2 == length) { if (pos2 == length) {
nonPositional.add(pos); nonPositional.add(pos);
break; break;
} }
char c = str.charAt(pos2++); char c = str.charAt(pos2++);
if (c == '%') { if (c == '%') {
continue; continue;
} }
if (c >= '0' && c <= '9' && pos2 < length) { if (c >= '0' && c <= '9' && pos2 < length) {
while ((c = str.charAt(pos2++)) >= '0' && c <= '9' && pos2 < length); while ((c = str.charAt(pos2++)) >= '0' && c <= '9' && pos2 < length);
if (c == '$') { if (c == '$') {
positional.add(pos); positional.add(pos);
continue; continue;
} }
} }
nonPositional.add(pos); nonPositional.add(pos);
if (nonPositional.size() >= nonPosMax) { if (nonPositional.size() >= nonPosMax) {
break; break;
} }
} }
return new Duo<>(nonPositional, positional); return new Duo<>(nonPositional, positional);
} }
private static boolean isPrintableChar(char c) { private static boolean isPrintableChar(char c) {
Character.UnicodeBlock block = Character.UnicodeBlock.of(c); Character.UnicodeBlock block = Character.UnicodeBlock.of(c);
return !Character.isISOControl(c) && c != KeyEvent.CHAR_UNDEFINED return !Character.isISOControl(c) && c != KeyEvent.CHAR_UNDEFINED
&& block != null && block != Character.UnicodeBlock.SPECIALS; && block != null && block != Character.UnicodeBlock.SPECIALS;
} }
} }

View File

@ -30,30 +30,30 @@ public class DebugInjector {
private int currParam; private int currParam;
private int lastParam; private int lastParam;
public static void inject(ListIterator<String> it, StringBuilder out) public static void inject(ListIterator<String> it, StringBuilder out)
throws AndrolibException { throws AndrolibException {
new DebugInjector(it, out).inject(); new DebugInjector(it, out).inject();
} }
private DebugInjector(ListIterator<String> it, StringBuilder out) { private DebugInjector(ListIterator<String> it, StringBuilder out) {
mIt = it; mIt = it;
mOut = out; mOut = out;
} }
private void inject() throws AndrolibException { private void inject() throws AndrolibException {
String definition = nextAndAppend(); String definition = nextAndAppend();
if (definition.contains(" abstract ") if (definition.contains(" abstract ")
|| definition.contains(" native ")) { || definition.contains(" native ")) {
nextAndAppend(); nextAndAppend();
return; return;
} }
parseParamsNumber(definition); parseParamsNumber(definition);
boolean end = false; boolean end = false;
while (!end) { while (!end) {
end = step(); end = step();
} }
} }
private void parseParamsNumber(String definition) throws AndrolibException { private void parseParamsNumber(String definition) throws AndrolibException {
int pos = definition.indexOf('('); int pos = definition.indexOf('(');
@ -78,34 +78,34 @@ public class DebugInjector {
} }
} }
private boolean step() { private boolean step() {
String line = next(); String line = next();
if (line.isEmpty()) { if (line.isEmpty()) {
return false; return false;
} }
switch (line.charAt(0)) { switch (line.charAt(0)) {
case '#': case '#':
return processComment(line); return processComment(line);
case ':': case ':':
append(line); append(line);
return false; return false;
case '.': case '.':
return processDirective(line); return processDirective(line);
default: default:
if (! areParamsInjected) { if (! areParamsInjected) {
injectRemainingParams(); injectRemainingParams();
} }
return processInstruction(line); return processInstruction(line);
} }
} }
private boolean processComment(String line) { private boolean processComment(String line) {
if (mFirstInstruction) { if (mFirstInstruction) {
return false; return false;
} }
Matcher m = REGISTER_INFO_PATTERN.matcher(line); Matcher m = REGISTER_INFO_PATTERN.matcher(line);
while (m.find()) { while (m.find()) {
String localName = m.group(1); String localName = m.group(1);
@ -145,7 +145,7 @@ public class DebugInjector {
break; break;
case "Float": case "Float":
localType = "F"; localType = "F";
break; break;
case "LongHi": case "LongHi":
case "LongLo": case "LongLo":
localType = "J"; localType = "J";
@ -167,11 +167,11 @@ public class DebugInjector {
.append('\n'); .append('\n');
} }
return false; return false;
} }
private boolean processDirective(String line) { private boolean processDirective(String line) {
String line2 = line.substring(1); String line2 = line.substring(1);
if (line2.startsWith("line ") || line2.startsWith("local ") || line2.startsWith("end local ")) { if (line2.startsWith("line ") || line2.startsWith("local ") || line2.startsWith("end local ")) {
return false; return false;
} }
@ -191,54 +191,54 @@ public class DebugInjector {
return false; return false;
} }
append(line); append(line);
if (line2.equals("end method")) { if (line2.equals("end method")) {
return true; return true;
} }
if (line2.startsWith("annotation ") || line2.equals("sparse-switch") if (line2.startsWith("annotation ") || line2.equals("sparse-switch")
|| line2.startsWith("packed-switch ") || line2.startsWith("packed-switch ")
|| line2.startsWith("array-data ")) { || line2.startsWith("array-data ")) {
while (true) { while (true) {
line2 = nextAndAppend(); line2 = nextAndAppend();
if (line2.startsWith(".end ")) { if (line2.startsWith(".end ")) {
break; break;
} }
} }
} }
return false; return false;
} }
private boolean processInstruction(String line) { private boolean processInstruction(String line) {
if (mFirstInstruction) { if (mFirstInstruction) {
mOut.append(".prologue\n"); mOut.append(".prologue\n");
mFirstInstruction = false; mFirstInstruction = false;
} }
mOut.append(".line ").append(mIt.nextIndex()).append('\n').append(line) mOut.append(".line ").append(mIt.nextIndex()).append('\n').append(line)
.append('\n'); .append('\n');
return false; return false;
} }
private String next() { private String next() {
return mIt.next().split("//", 2)[1].trim(); return mIt.next().split("//", 2)[1].trim();
} }
private String nextAndAppend() { private String nextAndAppend() {
String line = next(); String line = next();
append(line); append(line);
return line; return line;
} }
private void append(String append) { private void append(String append) {
mOut.append(append).append('\n'); mOut.append(append).append('\n');
} }
private final ListIterator<String> mIt; private final ListIterator<String> mIt;
private final StringBuilder mOut; private final StringBuilder mOut;
private boolean mFirstInstruction = true; private boolean mFirstInstruction = true;
private final Set<String> mInitializedRegisters = new HashSet<String>(); private final Set<String> mInitializedRegisters = new HashSet<String>();
private static final Pattern REGISTER_INFO_PATTERN = Pattern private static final Pattern REGISTER_INFO_PATTERN = Pattern
.compile("((?:p|v)\\d+)=\\(([^,)]+)([^)]*)\\);"); .compile("((?:p|v)\\d+)=\\(([^,)]+)([^)]*)\\);");
} }

View File

@ -36,37 +36,37 @@ import org.jf.dexlib2.writer.io.FileDataStore;
*/ */
public class SmaliBuilder { public class SmaliBuilder {
public static void build(ExtFile smaliDir, File dexFile, public static void build(ExtFile smaliDir, File dexFile,
HashMap<String, Boolean> flags) throws AndrolibException { HashMap<String, Boolean> flags) throws AndrolibException {
new SmaliBuilder(smaliDir, dexFile, flags).build(); new SmaliBuilder(smaliDir, dexFile, flags).build();
} }
private SmaliBuilder(ExtFile smaliDir, File dexFile, private SmaliBuilder(ExtFile smaliDir, File dexFile,
HashMap<String, Boolean> flags) { HashMap<String, Boolean> flags) {
mSmaliDir = smaliDir; mSmaliDir = smaliDir;
mDexFile = dexFile; mDexFile = dexFile;
mFlags = flags; mFlags = flags;
} }
private void build() throws AndrolibException { private void build() throws AndrolibException {
try { try {
DexBuilder dexBuilder = DexBuilder.makeDexBuilder(); DexBuilder dexBuilder = DexBuilder.makeDexBuilder();
for (String fileName : mSmaliDir.getDirectory().getFiles(true)) { for (String fileName : mSmaliDir.getDirectory().getFiles(true)) {
buildFile(fileName, dexBuilder); buildFile(fileName, dexBuilder);
} }
dexBuilder.writeTo(new FileDataStore( new File(mDexFile.getAbsolutePath()))); dexBuilder.writeTo(new FileDataStore( new File(mDexFile.getAbsolutePath())));
} catch (IOException | DirectoryException ex) { } catch (IOException | DirectoryException ex) {
throw new AndrolibException(ex); throw new AndrolibException(ex);
} }
} }
private void buildFile(String fileName, DexBuilder dexBuilder) throws AndrolibException, private void buildFile(String fileName, DexBuilder dexBuilder) throws AndrolibException,
IOException { IOException {
File inFile = new File(mSmaliDir, fileName); File inFile = new File(mSmaliDir, fileName);
InputStream inStream = new FileInputStream(inFile); InputStream inStream = new FileInputStream(inFile);
if (fileName.endsWith(".smali")) { if (fileName.endsWith(".smali")) {
try { try {
if (!SmaliMod.assembleSmaliFile(inFile,dexBuilder, false, false)) { if (!SmaliMod.assembleSmaliFile(inFile,dexBuilder, false, false)) {
throw new AndrolibException("Could not smali file: " + fileName); throw new AndrolibException("Could not smali file: " + fileName);
@ -74,41 +74,41 @@ public class SmaliBuilder {
} catch (IOException | RecognitionException ex) { } catch (IOException | RecognitionException ex) {
throw new AndrolibException(ex); throw new AndrolibException(ex);
} }
return; return;
} }
if (!fileName.endsWith(".java")) { if (!fileName.endsWith(".java")) {
LOGGER.warning("Unknown file type, ignoring: " + inFile); LOGGER.warning("Unknown file type, ignoring: " + inFile);
return; return;
} }
StringBuilder out = new StringBuilder(); StringBuilder out = new StringBuilder();
List<String> lines = IOUtils.readLines(inStream); List<String> lines = IOUtils.readLines(inStream);
if (!mFlags.get("debug")) { if (!mFlags.get("debug")) {
final String[] linesArray = lines.toArray(new String[0]); final String[] linesArray = lines.toArray(new String[0]);
for (int i = 1; i < linesArray.length - 1; i++) { for (int i = 1; i < linesArray.length - 1; i++) {
out.append(linesArray[i].split("//", 2)[1]).append('\n'); out.append(linesArray[i].split("//", 2)[1]).append('\n');
} }
} else { } else {
lines.remove(lines.size() - 1); lines.remove(lines.size() - 1);
ListIterator<String> it = lines.listIterator(1); ListIterator<String> it = lines.listIterator(1);
out.append(".source \"").append(inFile.getName()).append("\"\n"); out.append(".source \"").append(inFile.getName()).append("\"\n");
while (it.hasNext()) { while (it.hasNext()) {
String line = it.next().split("//", 2)[1].trim(); String line = it.next().split("//", 2)[1].trim();
if (line.isEmpty() || line.charAt(0) == '#' if (line.isEmpty() || line.charAt(0) == '#'
|| line.startsWith(".source")) { || line.startsWith(".source")) {
continue; continue;
} }
if (line.startsWith(".method ")) { if (line.startsWith(".method ")) {
it.previous(); it.previous();
DebugInjector.inject(it, out); DebugInjector.inject(it, out);
continue; continue;
} }
out.append(line).append('\n'); out.append(line).append('\n');
} }
} }
try { try {
if (!SmaliMod.assembleSmaliFile(out.toString(),dexBuilder, false, false, inFile)) { if (!SmaliMod.assembleSmaliFile(out.toString(),dexBuilder, false, false, inFile)) {
@ -117,12 +117,12 @@ public class SmaliBuilder {
} catch (IOException | RecognitionException ex) { } catch (IOException | RecognitionException ex) {
throw new AndrolibException(ex); throw new AndrolibException(ex);
} }
} }
private final ExtFile mSmaliDir; private final ExtFile mSmaliDir;
private final File mDexFile; private final File mDexFile;
private final HashMap<String, Boolean> mFlags; private final HashMap<String, Boolean> mFlags;
private final static Logger LOGGER = Logger.getLogger(SmaliBuilder.class private final static Logger LOGGER = Logger.getLogger(SmaliBuilder.class
.getName()); .getName());
} }

View File

@ -41,23 +41,23 @@ import java.nio.file.attribute.BasicFileAttributes;
*/ */
public class SmaliDecoder { public class SmaliDecoder {
public static void decode(File apkFile, File outDir, boolean debug, String debugLinePrefix, public static void decode(File apkFile, File outDir, boolean debug, String debugLinePrefix,
boolean bakdeb, int api) throws AndrolibException { boolean bakdeb, int api) throws AndrolibException {
new SmaliDecoder(apkFile, outDir, debug, debugLinePrefix, bakdeb, api).decode(); new SmaliDecoder(apkFile, outDir, debug, debugLinePrefix, bakdeb, api).decode();
} }
private SmaliDecoder(File apkFile, File outDir, boolean debug, String debugLinePrefix, private SmaliDecoder(File apkFile, File outDir, boolean debug, String debugLinePrefix,
boolean bakdeb, int api) { boolean bakdeb, int api) {
mApkFile = apkFile; mApkFile = apkFile;
mOutDir = outDir.toPath(); mOutDir = outDir.toPath();
mDebug = debug; mDebug = debug;
mDebugLinePrefix = debugLinePrefix; mDebugLinePrefix = debugLinePrefix;
mBakDeb = bakdeb; mBakDeb = bakdeb;
mApi = api; mApi = api;
} }
private void decode() throws AndrolibException { private void decode() throws AndrolibException {
try { try {
ClassPath.dontLoadClassPath = mDebug; ClassPath.dontLoadClassPath = mDebug;
baksmaliOptions options = new baksmaliOptions(); baksmaliOptions options = new baksmaliOptions();
@ -106,16 +106,16 @@ public class SmaliDecoder {
if (mDebug) { if (mDebug) {
Files.walkFileTree(mOutDir, new SmaliFileVisitor()); Files.walkFileTree(mOutDir, new SmaliFileVisitor());
} }
} catch (IOException ex) { } catch (IOException ex) {
throw new AndrolibException(ex); throw new AndrolibException(ex);
} }
} }
private final File mApkFile; private final File mApkFile;
private final Path mOutDir; private final Path mOutDir;
private final boolean mDebug; private final boolean mDebug;
private final String mDebugLinePrefix; private final String mDebugLinePrefix;
private final boolean mBakDeb; private final boolean mBakDeb;
private final int mApi; private final int mApi;

View File

@ -30,99 +30,99 @@ import java.util.List;
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/ */
public class TypeName { public class TypeName {
public final String package_; public final String package_;
public final String type; public final String type;
public final String innerType; public final String innerType;
public final int array; public final int array;
public TypeName(String type, int array) { public TypeName(String type, int array) {
this(null, type, null, array); this(null, type, null, array);
} }
public TypeName(String package_, String type, String innerType, int array) { public TypeName(String package_, String type, String innerType, int array) {
this.package_ = package_; this.package_ = package_;
this.type = type; this.type = type;
this.innerType = innerType; this.innerType = innerType;
this.array = array; this.array = array;
} }
public String getShortenedName() { public String getShortenedName() {
return getName("java.lang".equals(package_), isFileOwner()); return getName("java.lang".equals(package_), isFileOwner());
} }
public String getName() { public String getName() {
return getName(false, false); return getName(false, false);
} }
public String getName(boolean excludePackage, boolean separateInner) { public String getName(boolean excludePackage, boolean separateInner) {
String name = (package_ == null || excludePackage ? "" : package_ + '.') String name = (package_ == null || excludePackage ? "" : package_ + '.')
+ type + type
+ (innerType != null ? (separateInner ? '$' : '.') + innerType + (innerType != null ? (separateInner ? '$' : '.') + innerType
: ""); : "");
for (int i = 0; i < array; i++) { for (int i = 0; i < array; i++) {
name += "[]"; name += "[]";
} }
return name; return name;
} }
public String getJavaFilePath() { public String getJavaFilePath() {
return getFilePath(isFileOwner()) + ".java"; return getFilePath(isFileOwner()) + ".java";
} }
public String getSmaliFilePath() { public String getSmaliFilePath() {
return getFilePath(true) + ".smali"; return getFilePath(true) + ".smali";
} }
public String getFilePath(boolean separateInner) { public String getFilePath(boolean separateInner) {
return package_.replace('.', File.separatorChar) + File.separatorChar return package_.replace('.', File.separatorChar) + File.separatorChar
+ type + (separateInner && isInner() ? "$" + innerType : ""); + type + (separateInner && isInner() ? "$" + innerType : "");
} }
public boolean isInner() { public boolean isInner() {
return innerType != null; return innerType != null;
} }
public boolean isArray() { public boolean isArray() {
return array != 0; return array != 0;
} }
public boolean isFileOwner() { public boolean isFileOwner() {
if (mIsFileOwner == null) { if (mIsFileOwner == null) {
mIsFileOwner = true; mIsFileOwner = true;
if (isInner()) { if (isInner()) {
char c = innerType.charAt(0); char c = innerType.charAt(0);
if (c < '0' || c > '9') { if (c < '0' || c > '9') {
mIsFileOwner = false; mIsFileOwner = false;
} }
} }
} }
return mIsFileOwner; return mIsFileOwner;
} }
@Override @Override
public String toString() { public String toString() {
return getName(); return getName();
} }
public static TypeName fromInternalName(String internal) public static TypeName fromInternalName(String internal)
throws AndrolibException { throws AndrolibException {
Duo<TypeName, Integer> duo = fetchFromInternalName(internal); Duo<TypeName, Integer> duo = fetchFromInternalName(internal);
if (duo.m2 != internal.length()) { if (duo.m2 != internal.length()) {
throw new AndrolibException("Invalid internal name: " + internal); throw new AndrolibException("Invalid internal name: " + internal);
} }
return duo.m1; return duo.m1;
} }
public static List<TypeName> listFromInternalName(String internal) public static List<TypeName> listFromInternalName(String internal)
throws AndrolibException { throws AndrolibException {
List<TypeName> types = new ArrayList<TypeName>(); List<TypeName> types = new ArrayList<TypeName>();
while (!internal.isEmpty()) { while (!internal.isEmpty()) {
Duo<TypeName, Integer> duo = fetchFromInternalName(internal); Duo<TypeName, Integer> duo = fetchFromInternalName(internal);
types.add(duo.m1); types.add(duo.m1);
internal = internal.substring(duo.m2); internal = internal.substring(duo.m2);
} }
return types; return types;
} }
public static TypeName fromPath(Path path) { public static TypeName fromPath(Path path) {
List<String> parts = new ArrayList<>(path.getNameCount()); List<String> parts = new ArrayList<>(path.getNameCount());
@ -145,68 +145,68 @@ public class TypeName {
return new TypeName(Joiner.on('.').join(parts), type, innerType, array); return new TypeName(Joiner.on('.').join(parts), type, innerType, array);
} }
public static Duo<TypeName, Integer> fetchFromInternalName(String internal) public static Duo<TypeName, Integer> fetchFromInternalName(String internal)
throws AndrolibException { throws AndrolibException {
String origInternal = internal; String origInternal = internal;
int array = 0; int array = 0;
boolean isArray = false; boolean isArray = false;
do { do {
if (internal.isEmpty()) { if (internal.isEmpty()) {
throw new AndrolibException("Invalid internal name: " throw new AndrolibException("Invalid internal name: "
+ origInternal); + origInternal);
} }
isArray = internal.charAt(0) == '['; isArray = internal.charAt(0) == '[';
if (isArray) { if (isArray) {
array++; array++;
internal = internal.substring(1); internal = internal.substring(1);
} }
} while (isArray); } while (isArray);
int length = array + 1; int length = array + 1;
String type; String type;
switch (internal.charAt(0)) { switch (internal.charAt(0)) {
case 'B': case 'B':
type = "byte"; type = "byte";
break; break;
case 'C': case 'C':
type = "char"; type = "char";
break; break;
case 'D': case 'D':
type = "double"; type = "double";
break; break;
case 'F': case 'F':
type = "float"; type = "float";
break; break;
case 'I': case 'I':
type = "int"; type = "int";
break; break;
case 'J': case 'J':
type = "long"; type = "long";
break; break;
case 'S': case 'S':
type = "short"; type = "short";
break; break;
case 'Z': case 'Z':
type = "boolean"; type = "boolean";
break; break;
case 'V': case 'V':
type = "void"; type = "void";
break; break;
case 'L': case 'L':
int pos = internal.indexOf(';'); int pos = internal.indexOf(';');
if (pos == -1) { if (pos == -1) {
throw new AndrolibException("Invalid internal name: " throw new AndrolibException("Invalid internal name: "
+ origInternal); + origInternal);
} }
return new Duo<>(fromNameParts(Arrays.asList(internal.substring(1, pos).split("/")), array), length + pos); return new Duo<>(fromNameParts(Arrays.asList(internal.substring(1, pos).split("/")), array), length + pos);
default: default:
throw new AndrolibException("Invalid internal name: " throw new AndrolibException("Invalid internal name: "
+ origInternal); + origInternal);
} }
return new Duo<>(new TypeName(null, type, null, array), length); return new Duo<>(new TypeName(null, type, null, array), length);
} }
private Boolean mIsFileOwner; private Boolean mIsFileOwner;
} }