2023-01-01 07:23:24 -05:00

700 lines
24 KiB
Java
Executable File

/*
* Copyright (C) 2022 github.com/REAndroid
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.reandroid.lib.arsc.decoder;
import com.reandroid.lib.arsc.chunk.PackageBlock;
import com.reandroid.lib.arsc.chunk.TableBlock;
import com.reandroid.lib.arsc.group.EntryGroup;
import com.reandroid.lib.arsc.item.TableString;
import com.reandroid.lib.arsc.pool.TableStringPool;
import com.reandroid.lib.arsc.value.*;
import com.reandroid.lib.arsc.value.attribute.AttributeBag;
import com.reandroid.lib.common.EntryStore;
import java.util.Collection;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ValueDecoder {
public static boolean isReference(String txt){
if(txt==null){
return false;
}
if(isNullReference(txt)){
return true;
}
return PATTERN_REFERENCE.matcher(txt).matches();
}
private static boolean isNullReference(String txt){
if("@null".equals(txt)){
return true;
}
if("@empty".equals(txt)){
return true;
}
return false;
}
public static int encodeFloatOrDimension(String dimensionString){
if(dimensionString==null){
return 0;
}
Matcher matcher=PATTERN_DIMEN.matcher(dimensionString);
if(!matcher.find()){
return 0;
}
String sign = matcher.group(1);
String number = matcher.group(2);
String unit = matcher.group(3);
float value = Float.parseFloat(number);
if("-".equals(sign)){
value=-value;
}
return encodeFloatOrDimension(value, unit);
}
private static int encodeFloatOrDimension(float val, String unit){
if(unit==null||"".equals(unit)){
return Float.floatToIntBits(val);
}
int index=0;
if("%".equals(unit)||"%p".equals(unit)){
val=val/100.0f;
if("%p".equals(unit)){
index=1;
}
}else {
index=ValueDecoder.getDimensionIndex(unit);
}
int result = 0;
int shift = 0;
if(val!=0.0f){
for(int i=0;i<4;i++){
float fl = val/ValueDecoder.RADIX_MULTS[i];
int fl_int = (int)fl;
int last = (fl_int&0xff);
if(fl_int!=0 && last==0){
shift = i;
result = fl_int;
break;
}
}
}
shift=shift<<4;
result= result | shift;
result= result | index;
return result;
}
public static String decodeAttributeName(EntryStore store, PackageBlock currentPackage, int resourceId){
EntryGroup entryGroup=searchEntryGroup(store, currentPackage, resourceId);
if(entryGroup==null){
return String.format("@0x%08x", resourceId);
}
EntryBlock entryBlock=entryGroup.pickOne();
if(entryBlock==null){
return String.format("@0x%08x", resourceId);
}
String prefix=null;
if(currentPackage!=null){
String name=currentPackage.getName();
String other=entryBlock.getPackageBlock().getName();
if(!name.equals(other)){
prefix=other+":";
}
}
String name=entryGroup.getSpecName();
if(prefix!=null){
name=prefix+name;
}
return name;
}
public static String decodeAttribute(EntryStore store, int attrResId, int rawValue){
AttributeBag attributeBag = getAttributeBag(store, attrResId);
if(attributeBag==null){
return null;
}
return attributeBag.decodeAttributeValue(store, rawValue);
}
public static String decodeEntryValue(EntryStore store, PackageBlock currentPackage, ValueType valueType, int data){
if(store==null || currentPackage==null){
return null;
}
if(valueType==ValueType.STRING){
return decodeIntEntryString(currentPackage, data);
}
boolean is_reference=false;
boolean is_attribute=false;
if(valueType==ValueType.REFERENCE){
if(data==0){
return "@null";
}
is_reference=true;
}
if(valueType==ValueType.ATTRIBUTE){
if(data==0){
return "?null";
}
is_attribute=true;
}
if(is_reference || is_attribute){
String ref=buildReferenceValue(store, valueType, currentPackage.getName(), data);
if(ref!=null){
return ref;
}
char atOrQues=is_reference?'@':'?';
ref=atOrQues+toHexResourceId(data);
return ref;
}
return decode(valueType, data);
}
public static String decodeIntEntry(EntryStore store, EntryBlock entryBlock){
if(entryBlock==null){
return null;
}
BaseResValue baseResValue = entryBlock.getResValue();
if(!(baseResValue instanceof ResValueInt)){
return null;
}
ResValueInt resValueInt=(ResValueInt)baseResValue;
return decodeIntEntry(store, resValueInt);
}
public static String decodeIntEntry(EntryStore store, ResValueInt resValueInt){
if(resValueInt==null){
return null;
}
EntryBlock parentEntry=resValueInt.getEntryBlock();
if(parentEntry==null){
return null;
}
ValueType valueType=resValueInt.getValueType();
int data=resValueInt.getData();
return decodeIntEntry(store, parentEntry, valueType, data);
}
public static String decodeIntEntry(EntryStore store, ResValueBagItem bagItem){
if(bagItem==null){
return null;
}
EntryBlock parentEntry=bagItem.getEntryBlock();
if(parentEntry==null){
return null;
}
ValueType valueType=bagItem.getValueType();
int data=bagItem.getData();
return decodeIntEntry(store, parentEntry, valueType, data);
}
public static String decodeIntEntry(EntryStore store, EntryBlock parentEntry, ValueType valueType, int data){
if(valueType==ValueType.NULL){
return "@empty";
}
if(valueType==ValueType.STRING){
return decodeIntEntryString(parentEntry, data);
}
boolean is_reference=false;
boolean is_attribute=false;
if(valueType==ValueType.REFERENCE){
if(data==0){
return "@null";
}
is_reference=true;
}
if(valueType==ValueType.ATTRIBUTE){
if(data==0){
return "?null";
}
is_attribute=true;
}
if(is_reference || is_attribute){
String ref=buildReferenceValue(store, parentEntry, valueType, data);
if(ref!=null){
return ref;
}
char atOrQues=is_reference?'@':'?';
ref=atOrQues+toHexResourceId(data);
return ref;
}
return decode(valueType, data);
}
public static String buildReferenceValue(EntryStore store, EntryBlock entryBlock){
if(entryBlock==null){
return null;
}
BaseResValue baseResValue = entryBlock.getResValue();
if(!(baseResValue instanceof ResValueInt)){
return null;
}
ResValueInt resValueInt=(ResValueInt)baseResValue;
int resourceId=resValueInt.getData();
ValueType valueType=resValueInt.getValueType();
return buildReferenceValue(store, entryBlock, valueType, resourceId);
}
public static String decode(EntryStore entryStore, int currentPackageId, int nameResourceId, ValueType valueType, int rawVal){
String currPackageName=getPackageName(entryStore, currentPackageId);
String result=buildReferenceValue(entryStore, valueType, currPackageName, rawVal);
if(result!=null){
return result;
}
if(valueType==ValueType.STRING){
// Should not happen the string could be in ResXmlBlock, but if you are lazy here it goes
return decodeString(entryStore, currentPackageId, rawVal);
}
if(valueType==ValueType.FIRST_INT||valueType==ValueType.INT_HEX){
result=decodeAttribute(entryStore, nameResourceId, rawVal);
if(result!=null){
return result;
}
}
return decode(valueType, rawVal);
}
public static String decode(ValueType valueType, int data){
if(valueType==null){
return null;
}
switch (valueType){
case INT_BOOLEAN:
return decodeBoolean(data);
case FIRST_COLOR_INT:
case INT_COLOR_ARGB4:
case INT_COLOR_ARGB8:
case INT_COLOR_RGB4:
case INT_COLOR_RGB8:
case LAST_COLOR_INT:
return decodeColor(data);
case DIMENSION:
case FLOAT:
case FRACTION:
return decodeDimensionOrFloat(valueType, data);
case INT_HEX:
return decodeHex(data);
case INT_DEC:
case FIRST_INT:
case LAST_INT:
return decodeInt(data);
}
return null;
}
public static String buildReference(String currentPackageName,
String referredPackageName,
char atOrQues,
String typeName,
String resourceName){
StringBuilder builder=new StringBuilder();
if(atOrQues!=0){
builder.append(atOrQues);
}
if(!isEqualString(currentPackageName, referredPackageName)){
if(!isEmpty(currentPackageName) && !isEmpty(referredPackageName)){
builder.append(referredPackageName);
if(!referredPackageName.endsWith(":")){
builder.append(':');
}
}
}
if(!isEmpty(typeName)){
builder.append(typeName);
builder.append('/');
}
builder.append(resourceName);
return builder.toString();
}
private static String buildReferenceValue(EntryStore store, EntryBlock entryBlock, ValueType valueType, int resourceId){
if(entryBlock==null){
return null;
}
EntryGroup value=searchEntryGroup(store, entryBlock, resourceId);
if(value==null){
return null;
}
return buildReferenceValue(valueType, entryBlock, value);
}
private static String buildReferenceValue(ValueType valueType, EntryBlock entryBlock, EntryGroup value){
char atOrQues;
if(valueType==ValueType.REFERENCE){
atOrQues='@';
}else if(valueType==ValueType.ATTRIBUTE){
atOrQues='?';
}else {
atOrQues=0;
}
String currentPackageName=getPackageName(entryBlock);
String referredPackageName=getPackageName(value);
String typeName=value.getTypeName();
String name=value.getSpecName();
return buildReference(currentPackageName, referredPackageName, atOrQues, typeName, name);
}
private static String buildReferenceValue(EntryStore entryStore, ValueType valueType, String currentPackageName, int resourceId){
char atOrQues;
if(valueType==ValueType.REFERENCE){
if(resourceId==0){
return "@null";
}
atOrQues='@';
}else if(valueType==ValueType.ATTRIBUTE){
if(resourceId==0){
return "@empty";
}
atOrQues='?';
}else {
return null;
}
EntryGroup value=null;
if(entryStore!=null){
value=entryStore.getEntryGroup(resourceId);
}
if(value==null){
return atOrQues+toHexResourceId(resourceId);
}
String referredPackageName=getPackageName(value);
String typeName=value.getTypeName();
String name=value.getSpecName();
return buildReference(currentPackageName, referredPackageName, atOrQues, typeName, name);
}
private static String getPackageName(EntryStore entryStore, int packageOrResourceId){
if(entryStore==null || packageOrResourceId==0){
return null;
}
int pkgId=(packageOrResourceId>>24)&0xFF;
if(pkgId==0){
pkgId=packageOrResourceId;
}
Collection<PackageBlock> allPkg = entryStore.getPackageBlocks((byte) pkgId);
if(allPkg==null){
return null;
}
for(PackageBlock packageBlock:allPkg){
String name=packageBlock.getName();
if(name!=null){
return name;
}
}
return null;
}
private static String getPackageName(EntryGroup entryGroup){
if(entryGroup==null){
return null;
}
return getPackageName(entryGroup.pickOne());
}
private static String getPackageName(EntryBlock entryBlock){
if(entryBlock==null){
return null;
}
PackageBlock packageBlock=entryBlock.getPackageBlock();
if(packageBlock==null){
return null;
}
return packageBlock.getName();
}
private static EntryGroup searchEntryGroup(EntryStore store, EntryBlock entryBlock, int resourceId){
EntryGroup entryGroup=searchEntryGroup(entryBlock, resourceId);
if(entryGroup!=null){
return entryGroup;
}
if(store==null){
return null;
}
return store.getEntryGroup(resourceId);
}
private static EntryGroup searchEntryGroup(EntryBlock entryBlock, int resourceId){
if(entryBlock==null){
return null;
}
PackageBlock packageBlock=entryBlock.getPackageBlock();
if(packageBlock==null){
return null;
}
TableBlock tableBlock=packageBlock.getTableBlock();
if(tableBlock==null){
return null;
}
for(PackageBlock pkg:tableBlock.listPackages()){
EntryGroup entryGroup=pkg.getEntryGroup(resourceId);
if(entryGroup!=null){
return entryGroup;
}
}
return null;
}
private static EntryGroup searchEntryGroup(EntryStore store, PackageBlock packageBlock, int resourceId){
if(packageBlock!=null){
TableBlock tableBlock=packageBlock.getTableBlock();
if(tableBlock!=null){
for(PackageBlock pkg:tableBlock.listPackages()){
EntryGroup entryGroup=pkg.getEntryGroup(resourceId);
if(entryGroup!=null){
return entryGroup;
}
}
}
}
if(store!=null){
return store.getEntryGroup(resourceId);
}
return null;
}
private static String decodeIntEntryString(EntryBlock entryBlock, int data){
if(entryBlock==null){
return null;
}
PackageBlock packageBlock=entryBlock.getPackageBlock();
if(packageBlock==null){
return null;
}
TableBlock tableBlock=packageBlock.getTableBlock();
if(tableBlock==null){
return null;
}
TableStringPool pool = tableBlock.getTableStringPool();
TableString tableString=pool.get(data);
if(tableString==null){
return null;
}
return tableString.getHtml();
}
private static String decodeString(EntryStore entryStore, int packageOrResourceId, int stringRef){
if(entryStore==null||packageOrResourceId==0){
return null;
}
int pkgId=(packageOrResourceId>>24)&0xFF;
if(pkgId==0){
pkgId=packageOrResourceId;
}
Collection<PackageBlock> allPkg = entryStore.getPackageBlocks((byte) pkgId);
if(allPkg==null){
return null;
}
TableString tableString=null;
for(PackageBlock packageBlock:allPkg){
TableBlock tableBlock=packageBlock.getTableBlock();
if(tableBlock==null){
continue;
}
TableString ts=tableBlock.getTableStringPool().get(stringRef);
if(ts==null){
continue;
}
if(tableString==null){
tableString=ts;
}else {
// Duplicate result, could be from split apks
return null;
}
}
if(tableString!=null){
return tableString.getHtml();
}
return null;
}
private static String decodeIntEntryString(PackageBlock packageBlock, int data){
if(packageBlock==null){
return null;
}
TableBlock tableBlock=packageBlock.getTableBlock();
if(tableBlock==null){
return null;
}
TableStringPool pool = tableBlock.getTableStringPool();
TableString tableString=pool.get(data);
if(tableString==null){
return null;
}
return tableString.getHtml();
}
private static String decodeHex(int rawVal){
return String.format("0x%x", rawVal);
}
private static String decodeInt(int rawVal){
return String.valueOf(rawVal);
}
private static String decodeBoolean(int data){
if(data == 0xFFFFFFFF){
return "true";
}
return "false";
}
private static String decodeColor(int rawVal){
String hex=String.format("%x", rawVal);
if(hex.length()<=6){
return String.format("#%06x", rawVal);
}
return String.format("#%08x", rawVal);
}
private static String decodeDimensionOrFloat(ValueType valueType, int rawVal){
return decodeFloat(rawVal, valueType);
}
private static String decodeFloat(int val, ValueType valueType){
if(valueType==ValueType.FLOAT){
float f=Float.intBitsToFloat(val);
return Float.toString(f);
}
float f=complexToFloat(val);
String unit="";
switch (valueType){
case FRACTION:
f=f*100;
if((val & 0x3)==0){
unit="%";
}else {
unit="%p";
}
break;
case DIMENSION:
int i=(val & 0xf);
unit=getDimensionUnit(i);
break;
}
return Float.toString(f)+unit;
}
private static float complexToFloat(int complex) {
int y=(complex >> 4) & 0x3;
float result=complex & 0xffffff00;
float y2=RADIX_MULTS[y];
result=result * y2;
return result;
}
static String getDimensionUnit(int index){
if(index<0 || index>DIMENSION_UNIT_STRS.length){
index=1;
}
return DIMENSION_UNIT_STRS[index];
}
static int getDimensionIndex(String unit){
String[] dims=DIMENSION_UNIT_STRS;
for(int i=0;i<dims.length;i++){
if(dims[i].equals(unit)){
return i;
}
}
return 0;
}
private static String getResourceName(EntryStore store, EntryBlock entryBlock, int resourceId){
if(entryBlock!=null){
EntryGroup group=searchEntryGroup(entryBlock, resourceId);
if(group!=null){
String name=group.getSpecName();
if(name!=null){
return name;
}
}
}
if(store==null){
return null;
}
Collection<EntryGroup> foundGroups = store.getEntryGroups(resourceId);
return pickResourceName(foundGroups);
}
private static String pickResourceName(Collection<EntryGroup> groups){
if(groups==null){
return null;
}
for(EntryGroup entryGroup:groups){
String name=entryGroup.getSpecName();
if(name!=null){
return name;
}
}
return null;
}
private static AttributeBag getAttributeBag(EntryStore store, int resourceId){
ResValueBag resValueBag=getAttributeValueBag(store, resourceId);
if(resValueBag==null){
return null;
}
return AttributeBag.create(resValueBag);
}
private static ResValueBag getAttributeValueBag(EntryStore store, int resourceId){
if(store==null){
return null;
}
Collection<EntryGroup> foundGroups = store.getEntryGroups(resourceId);
ResValueBag best=null;
for(EntryGroup group:foundGroups){
ResValueBag valueBag= getAttributeValueBag(group);
best=chooseBest(best, valueBag);
}
return best;
}
private static ResValueBag getAttributeValueBag(EntryGroup entryGroup){
if(entryGroup==null){
return null;
}
ResValueBag best=null;
Iterator<EntryBlock> iterator=entryGroup.iterator(true);
while (iterator.hasNext()){
EntryBlock entryBlock=iterator.next();
ResValueBag valueBag= getAttributeValueBag(entryBlock);
best=chooseBest(best, valueBag);
}
return best;
}
private static ResValueBag getAttributeValueBag(EntryBlock entryBlock){
if(entryBlock==null){
return null;
}
BaseResValue baseResValue = entryBlock.getResValue();
if(baseResValue instanceof ResValueBag){
return (ResValueBag) baseResValue;
}
return null;
}
private static ResValueBag chooseBest(ResValueBag valueBag1, ResValueBag valueBag2){
if(valueBag1==null){
return valueBag2;
}
if(valueBag2==null){
return valueBag1;
}
if(valueBag2.getCount()>valueBag1.getCount()){
return valueBag2;
}
return valueBag1;
}
private static String toHexResourceId(int resourceId){
return String.format("0x%08x", resourceId);
}
private static boolean isEqualString(String str1, String str2){
if(isEmpty(str1)){
return isEmpty(str2);
}
return str1.equals(str2);
}
private static boolean isEmpty(String str){
if(str==null){
return true;
}
str=str.trim();
return str.length()==0;
}
private static final String[] DIMENSION_UNIT_STRS = new String[] { "px", "dip", "sp", "pt", "in", "mm" };
private static final float MANTISSA_MULT = 1.0f / (1 << 8);
static final float[] RADIX_MULTS = new float[] {
1.0f * MANTISSA_MULT, 1.0f / (1 << 7) * MANTISSA_MULT,
1.0f / (1 << 15) * MANTISSA_MULT, 1.0f / (1 << 23) * MANTISSA_MULT };
private static final Pattern PATTERN_COLOR = Pattern.compile("^#([0-9a-fA-F]{6,8})$");
private static final Pattern PATTERN_DIMEN = Pattern.compile("^(-?)([0-9]+\\.[0-9E\\-+]+)([dimnpstx%]{0,3})$");
private static final Pattern PATTERN_INTEGER = Pattern.compile("^(-?)([0-9]+)$");
private static final Pattern PATTERN_HEX = Pattern.compile("^0x[0-9a-fA-F]+$");
public static final Pattern PATTERN_REFERENCE = Pattern.compile("^([?@])(([^\\s:@?/]+:)?)([^\\s:@?/]+)/([^\\s:@?/]+)$");
}